Boost库学习笔记(3)—— Fiber

本文介绍Boost.Fiber库的原理及使用方法,详细解释了纤程(微线程)的概念、特性及其与线程的区别。同时涵盖了如何启动、管理纤程,自定义调度器,以及同步机制等内容。

一、概述

        Boost.Fiber是一种微线程(用户层),也可以叫作纤程(协程),与一般的协程相比,其内部提供了调度管理器。每个fiber都有自己的栈,它可以保存当前的执行状态,包括所有寄存器的CPU标志、指令指针和堆栈指针,然后可以从此状态恢复。其目的是在单个线程上通过协作调度运行多个可执行序列(即函数)。正在运行的fiber可以明确的决定什么时候yield,从而允许另外一个fiber运行(上下文切换)。
        要使用Fiber库,只需要代码中包含头文件:#include <boost/fiber/all.hpp>

二、Fiber和线程

        在x86上,线程之间的上下文切换通常要花费数千个CPU周期,而fiber之间的切换只有不到100个周期。fiber在任意时间点都是运行在单一的线程上。
        在指定线程上启动的所有fiber,控制指令在它们之间协作传递。在指定的线程上,任意时刻,最多只有一个fiber在运行。
        尽管可以更有效的使用内核,但是在线程上创建fiber,并不会把程序分布到更多的硬件内核上。
        另一方面,fiber可以安全的访问父线程独占的任何资源,不需要显示的对该资源进行保护,防止同一线程上的其他fiber并发访问该资源。我们可以得到保证该线程上没有其他的fiber并发的接触该资源。要在历史遗留代码中引发并发性,这一方面很重要。通过使用异步I/O交替执行,我们可以安全创建用于运行旧代码的fiber。
        实际上,fiber提供了一种基于异步I/O来组织并发代码的方式。在fiber运行的代码可以使其看起来像调用一个普通的阻塞函数,这种调用可以很方便的挂起调用的fiber,从而允许同一个线程上的其他fiber运行。当操作完成时,挂起的fiber将恢复运行,而不必显示的保持或恢复其状态,它的本地堆栈变量在整个调用中中时持久存在的。
        fiber可以从一个线程迁移到另一个线程,默认情况下库不会这样处理。但是我们可以自定义在线程之间迁移fiber的调度器,可以自定义fiber属性,以协助调度器决定运行迁移哪些fiber。
        在fiber上调用阻塞I/O接口将会阻塞它所在的线程,我们建议在fiber上使用异步I/O接口。Boost.Asio和其他异步I/O操作可以直接适用于Boost.Fiber。

三、boost::fibers::fiber

        每个boost::fibers::fiber对象表示一个微线程,调度器将会启动和管理该fiber对象。

boost::fibers::fiber f1; // not-a-fiber

void f() {
   
   
    boost::fibers::fiber f2( some_fn);
    f1 = std::move( f2); // f2 moved to f1
}

1、启动

        可以通过向构造函数传入一个可调用类型的对象(比如lambda)来启动一个新的fiber。如果对象不可拷贝或者不可move,那么可以使用std::ref对该对象进行引用,这种情况下,必须保证被应用的该对象生命周期比新创建的fiber长。

struct callable {
   
   
    void operator()();
};

boost::fibers::fiber copies_are_safe() {
   
   
    callable x;
    return boost::fibers::fiber(x);
} // 函数执行后 x 对象被销毁,但是新创建的fiber对 x 有了一份拷贝,所以这样是可以的

boost::fibers::fiber oops() {
   
   
    callable x;
    return boost::fibers::fiber(std::ref(x));
} // 函数执行后 x 对象被销毁,但是新创建的fiber仍然对 x 进行了引用,这将导致未定义行为

        新创建的fiber不会立即开始执行,它会在准备运行的fiber列表中排队,当调度器找到它时,它才会开始运行。

2、异常

        传入fiber构造函数的可调用对象或者函数,如果其内部产生了异常,则构造函数将调用std::terminate()。如果需要知道抛了某种异常,可以使用future<>或者packaged_task<>

3、detach

        fiber可以通过显示调用成员函数detach()来进行分离。当调用了detach()后,该fiber变为not-a-fiber,然后它可以被安全的销毁。

void some_fn() {
   
   
    ...
}
boost::fibers::fiber(some_fn).detach();

        Boost.Fiber提供了许多用于等待fiber完成的方法。我们甚至可以使用mutex、condition_variable或者任意其他库提供的同步对象来和已被分离的fiber对象进行协调。如果当线程的主fiber终止时,已分离的fiber仍在运行,则该线程不会被关闭。

4、join

        为了等待fiber结束,可以使用成员函数join(),它将阻塞至fiber对象完成。如果fiber已经完成,那么join将立即返回,该fiber对象变为not-a-fiber。

void some_fn() {
   
   
    ...
}
boost::fibers::fiber f(some_fn);
...
f.join();

5、析构

        当fiber对象还有有效可执行的上下文(即fiber是joinable())时,如果它被销毁,程序将会终止。如果希望fiber比启动它的对象存活更久,那么请使用detach()方法。

void some_fn() {
   
   
    ...
}

{
   
   
    boost::fibers::fiber f(some_fn);
} // std::terminate() 将被调用

{
   
   
    boost::fibers::fiber f(some_fn);
    f.detach();
} // 没问题,程序继续执行

6、ID

        类fiber::id的对象可以用来标识fiber。每一个运行的fiber都有一个唯一的fiber::id,通过调用get_id()成员函数,可以获取对应fiber的id。类fiber::id的对象是可以拷贝的,可以作为关联容器中的键值(它提供了所有的比较运算符),也可以使用流插入操作符将它们写入输出流(输出格式未指定)。
        每个fiber::id的实例要么指向fiber对象,要么指向not-a-fiber,指向not-a-fiber的实例彼此相等,但指向实际fiber对象的实例则不相等。

7、枚举类型 - launch

        指定控制是否立即传递到新启动的fiber。

enum class launch {
   
   
    dispatch,
    post // 默认值
};
  • dispatch:立即运行,换句话说,启动一个新fiber将挂起调用者(之前运行的fiber),被挂起的fiber将等待调度器稍后寻找机会唤醒它。
  • post:被传给调度器并设置为就绪状态(但还未运行),调用者继续运行(之前运行的fiber),新fiber将等待调度器稍后寻找机会唤醒它时才运行。

四、调度

        线程中的fiber是由fiber管理器协调的。fiber的控制是合作性的,而不是先发制人:每当一个fiber挂起(或yield)时,fiber管理器会向调度器咨询下一个将运行那个fiber。
        Boost.Fiber提供了fiber管理器,但是调度器是可以

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值