相关文章推荐
善良的荔枝  ·  new operator - Create ...·  1 年前    · 

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

        可以通过向构造函数传入一个可调用类型的对象(比如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列表中排队,当调度器找到它时,它才会开始运行。

        传入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();

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

void some_fn() {
    ...
    boost::fibers::fiber f(some_fn);
} // std::terminate() 将被调用
    boost::fibers::fiber f(some_fn);
    f.detach();
} // 没问题,程序继续执行

        类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管理器,但是调度器是可以自行定制的。
        每个线程都有自己的调度器,进程中不同的线程可以使用不同的调度器,默认情况下,Boost.Fiber为每个线程隐式设置了round_robin的实例作为调度器。
        我们可以显示的编写自己的algorithm子类,大多数情况下,我们的algorithm子类不需要预防跨线程调用:fiber管理器会拦截并延迟这样的调用。大多数algorithm方法只直接从它所管理的线程中调用。
        特例情况如下:通过调用use_scheduling_algorithm()接口,让algorithm子类在一个特定线程上运行。

void thread_fn() {
    boost::fibers::use_scheduling_algorithm<my_fiber_scheduler>();
    ...

        调度器类必现实现algorithm接口。Boost.Fiber提供的调度器有:round_robinwork_stealingnuma::work_stealingshared_work

void my_thread(std::uint32_t thread_count) {
    // 线程注册 work-stealing 作为自身调度器
    boost::fibers::use_scheduling_algorithm< boost::fibers::algo::work_stealing >(thread_count);
    ...
// 逻辑CPU的个数
std::uint32_t thread_count = std::thread::hardware_concurrency();
// 首先启动工作线程
std::vector<std::thread> threads;
for ( std::uint32_t i = 1; i < thread_count; ++i) {
    // 创建线程
    threads.emplace_back(my_thread, thread_count);
// 线程注册 work-stealing 作为自身调度器
boost::fibers::use_scheduling_algorithm<boost::fibers::algo::work_stealing >(thread_count);
...

        这个示例创建了std::thread::hardware_concurrency()返回的线程数。每个线程运行一个work_stealing调度器。每个调度器的实例需要知道在程序中有多少个线程运行了work-stealing调度器。如果线程的本地队列用完了就绪的fiber,则该线程会尝从其他同样运行该调度器的线程中获取就绪的fiber。

1、algorithm

        algorithm是定义一个fiber调度器锁所必须实现的抽象接口基类。

#include <boost/fiber/algo/algorithm.hpp>
namespace boost {
namespace fibers {
namespace algo {
struct algorithm {
    virtual ~algorithm();
    virtual void awakened(context *) noexcept = 0;
    virtual context * pick_next() noexcept = 0;
    virtual bool has_ready_fibers() const noexcept = 0;
    virtual void suspend_until(std::chrono::steady_clock::time_point const&) noexcept = 0;
    virtual void notify() noexcept = 0;
}}}

2、自定义调度器Fiber属性

        一个从algorithm直接派生的调度器类,可以使用于实现algorithm接口的上下文的任意信息。但是自定义的调度器可能需要追踪fiber的其他属性。例如,一个基于优先级的调度器可能需要追踪fiber的优先级。Boost.Fiber提供了一种机制,自定义调度器可以将自身的属性和每个fiber进行关联。自定义的fiber属性类必须从fiber_properties派生。

#include <boost/fiber/properties.hpp>
namespace boost {
namespace fibers {
class fiber_properties {
public:
    fiber_properties(context *) noexcept;
    virtual ~fiber_properties();
protected:
    void notify() noexcept;

五、堆栈分配

        fiber使用内部的__econtext__用于管理一组寄存器和堆栈。堆栈使用的内存时通过一个stack_allocator进行allocated/deallocated,这是建模stack-allocator概念所必须的。stack_allocator将被传入fiber::fiber()fibers::async()

        通常,Boost.Fiber同步对象既不能拷贝也不能移动。同步对象充当不同fiber之间相互约定的集合点。如果将这个对象拷贝到其他地方,那么新的拷贝将没有使用者。如果将这个对象移动到其他地方,那么将是原始实例处于未指定的状态,现有的使用者的行为将会很奇怪。
        默认情况下,库所提供的fiber同步对象将运行在不同的线程上安全的同步fiber。然而,通过定义BOOST_FIBERS_NO_ATOMICS来构建库时,可以删除这种级别的同步(为了提高性能)。当使用该宏构建库时,必须确保所有的fiber引用的指定同步对象都运行在同一个线程上。

1、锁类型

  • boost::fibers::mutex
  • boost::fibers::timed_mutex
  • boost::fibers::recursive_mutex
  • boost::fibers::recursive_timed_mutex

2、条件变量

3、Barrier

4、Channel

        channel是一种通过消息传递来通信和同步执行线程的模型。channel操作返回的channel枚举状态:

enum class channel_op_status {
    success, // 操作成功
    empty, // channel为空,操作失败
    full, // channel已满,操作失败
    closed, // channel已关闭,操作失败
    timeout // 在指定超时时间发生前,操作还未准备好
  • boost::fibers::buffered_channel
    Boost.Fiber提供了一个有边界、缓冲的channel(MPMC队列),它适合通过异步消息传递来同步fiber,可运行在相同或不同的线程上。
  • boost::fibers::unbuffered_channel
    Boost.Fiber提供了unbuffered_channel模板,该模板适合通过同步消息传递来同步fiber,可运行在相同或不同的线程上。fiber等待消费某个值时将阻塞,知道该值被生产。如果fiber试图通过unbuffered_channel发送某值,且没有fiber在等待接收该值时,channel将阻塞发送的fiber。

5、futures

七、简单的封装

头文件:fiber_executor.h

#ifndef __FIBER_EXECUTOR_H__
#define __FIBER_EXECUTOR_H__
#include <atomic>
#include <memory>
#include <thread>
#include <boost/fiber/all.hpp>
 * Brief: 协程程序, 执行具体的代码逻辑
class FiberProc
	friend class FiberExecutor;
public:
	 * Brief:	构造函数
	 * Param:	func - 协程程序函数, 若设置且没有重写run, 则会执行该函数, 若重写了run, 则会执行重写的run里面的具体代码逻辑
	 *			stackSize - 协程的栈空间大小
	 * Return:	None
	FiberProc(const std::function<void()>& func = nullptr, std::size_t stackSize = (512 * 1024));
	virtual ~FiberProc(void);
	 * Brief:	运行, 子类可以重写该接口来编写执行的代码逻辑
	 * Param:	void
	 * Return:	void
	virtual void run(void);
	 * Brief:	取消
	 * Param:	void
	 * Return:	void
    void cancel(void);
	 * Brief:	是否已取消
	 * Param:	void
	 * Return:	true - 已取消, false - 未取消
    bool isCancelled(void);
private: /* noncopale */
	FiberProc(const FiberProc&) = default;
	FiberProc& operator=(const FiberProc&) = default;
private:
	std::function<void()> m_func;
	std::size_t m_stackSize;
    std::atomic_bool m_cancelled;
 * Brief: 协程执行者, 内部会创建一个线程用于运行协程程序
class FiberExecutor final
public:
	 * Brief:	构造函数
	 * Param:	channelCapacity - 协程队列容量
	 * Return:	None
	FiberExecutor(std::size_t channelCapacity = 1024);
	virtual ~FiberExecutor(void);
	 * Brief:	等待退出(调用该接口会阻塞直到线程中的所有协程都执行完毕才会继续后续流程)
	 * Param:	void
	 * Return:	void
	void join(void);
	 * Brief:	把协程程序对象加入当前队列
	 * Param:	procObj - 协程程序对象
	 * Return:	协程程序对象(和入参一样)
	std::shared_ptr<FiberProc> post(const std::shared_ptr<FiberProc>& procObj);
	 * Brief:	把协程程序函数加入当前队列
	 * Param:	procFunc - 协程程序函数
	 * Return:	协程程序对象
	std::shared_ptr<FiberProc> post(const std::function<void()>& procFunc);
private: /* noncopale */
	FiberExecutor(const FiberExecutor&) = default;
	FiberExecutor& operator=(const FiberExecutor&) = default;
private:
	std::unique_ptr<boost::fibers::buffered_channel<std::shared_ptr<FiberProc>>> m_channel; /* 有边界, 有序的通道 */
	std::unique_ptr<std::thread> m_thread; /* 专门用于运行通道队列里的协程程序 */
#endif /* __FIBER_EXECUTOR_H__ */

源文件:fiber_executor.cpp

#include "fiber_executor.h"
FiberProc::FiberProc(const std::function<void()>& func, std::size_t stackSize)
: m_func(func)
, m_stackSize(stackSize)
, m_cancelled(false)
FiberProc::~FiberProc(void)
void FiberProc::run(void)
	if (m_func)
		m_func();
void FiberProc::cancel(void)
    m_cancelled = true;
bool FiberProc::isCancelled(void)
    return m_cancelled;
FiberExecutor::FiberExecutor(std::size_t channelCapacity)
	assert(channelCapacity >= 2);
	m_channel = std::make_unique<boost::fibers::buffered_channel<std::shared_ptr<FiberProc>>>(channelCapacity);
	std::promise<void> result;
	m_thread = std::make_unique<std::thread>([this, &result] {
		result.set_value();
		std::shared_ptr<FiberProc> proc;
		while (boost::fibers::channel_op_status::closed != m_channel->pop(proc)) /* 循环从任务队列中取出任务 */
			/* 创建fiber并运行 */
			boost::fibers::fiber(std::allocator_arg, boost::fibers::default_stack(proc->m_stackSize), [proc]() {
                if (!proc->isCancelled()) {
                    proc->run();
            }).detach();
    });	/* 当前线程会等待所有fiber结束才会退出 */
	result.get_future().get();
FiberExecutor::~FiberExecutor(void)
	m_channel->close();
	m_thread->join();
void FiberExecutor::join(void)
	m_thread->join();
std::shared_ptr<FiberProc> FiberExecutor::post(const std::shared_ptr<FiberProc>& procObj)
	m_channel->push(procObj);
    return procObj;
std::shared_ptr<FiberProc> FiberExecutor::post(const std::function<void()>& procFunc)
    std::shared_ptr<FiberProc> procObj = std::make_shared<FiberProc>(procFunc);
	m_channel->push(procObj);
    return procObj;
#include <chrono>
#include <iostream>
#include <string>
#include "fiber_executor.h"
static FiberExecutor s_executor(16);
void print(int num)
	for (int i = 0; i < 2; ++i)
		std::cout << "--- num: " << num << ", i: " << i << " --- start\n";
		if (0 == num % 2) {
			boost::fibers::promise<void> result;
			s_executor.post([num, i, &result] { // 耗时操作最好放在其他线程执行, 否则会阻塞掉协程所在线程
				std::cout << "--- num: " << num << ", i: " << i << " --- sleep\n";
				std::this_thread::sleep_for(std::chrono::milliseconds(1000));
				result.set_value();
			});
			result.get_future().get();
		std::cout << "--- num: " << num << ", i: " << i << " --- end\n";
int main()
	for (int num = 0; num < 10; ++num)
		std::cout << "++++++++++ " << num << " +++ start\n";
		s_executor.post([num] {
			print(num);
		});
		std::cout << "++++++++++ " << num << " +++ end\n";
		std::this_thread::sleep_for(std::chrono::milliseconds(50));
	return 0;
                    一、概述&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Boost.Fiber是一种微线程(用户层),也可以叫作纤程(协程),与一般的协程相比,其内部提供了调度管理器。每个fiber都有自己的栈,它可以保存当前的执行状态,包括所有寄存器的CPU标志、指令指针和堆栈指针,然后可以从此状态恢复。其目的是在单个线程上通过协作调度运行多个可执行序列(即函数)。正在运行的fiber可以明确的决定什么时候yield,来允许另外一个fiber运行(上下文切换)。在x86上
				
boost::fibers::launch::dispatch的测试程序实现功能C++实现代码 boost::fibers::launch::dispatch的测试程序 C++实现代码 #include <boost/assert.hpp> #include <boost/test/unit_test.hpp> #include <boost/fiber/all.hpp> #include <chrono> #include <sstream&
上下文简介 首先要理解boost::context的概念和应用场景。程序在执行的时候,当前的执行的代码环境和所处的状态,就是context。boost::context保留了当前程序执行时的寄存器等的状态,可以认为是一个上下文A;然后线程就可以去执行其他的代码,完成执行后,可以切回上下文A,并从当初切走的地方执行,而且当初的上下文的现场不变,这就是完成了一次真正意义上的上下文切换。 上线文切换,在协程和用户态线程等有重要的意义(统称它们为routine),我们可以启动一定数量的操作系统的线程,然后让rout
boost::fibers::algo::shared_work >用法的测试程序实现功能C++实现代码 boost::fibers::algo::shared_work >用法的测试程序 C++实现代码 #include <boost/assert.hpp> #include <boost/fiber/all.hpp> #include <boost/fiber/detail/thread_barrier.hpp> #include <chr
Boost是一个流行的C++,是C++标准的一个有益补充。在使用时我们需要注意Boost的版本与编译器的兼容性。 在这里,"msvc14.2"是指Microsoft Visual Studio编译器的版本号。目前而言,最新的msvc(Microsoft Visual C++)版本是14.2,对应于Visual Studio 2019。如果我们想在Visual Studio 2019中使用Boost,我们需要下载适用于该版本的Boost源代码,并在Visual Studio 2019中编译安装。 首先,我们需要从Boost官方网站(https://www.boost.org/)下载最新的Boost源代码。接着,我们需要解压缩下载的代码,并使用Visual Studio 2019打开解压后的文件夹。 然后,在Visual Studio 2019中打开解压后的Boost源代码文件夹,我们可以在其中找到一个名为"bootstrap.bat"的批处理文件。我们需要双击运行该文件,这将会通过运行b2工具,自动为我们生成编译配置。 接下来,我们需要使用命令提示符窗口(Command Prompt)进入到Boost源代码文件夹的根目录,并运行以下命令: ```shell 这将会开始编译并安装Boost。编译过程可能需要一些时间,取决于你的计算机性能和Boost的组件数量。完成后,我们便成功安装了Boost。 最后,我们可以在Visual Studio 2019中新建一个C++项目,并在项目属性中配置Boost的路径。这样,我们就可以在项目中引入Boost的头文件并使用其中的功能了。 总而言之,对于msvc14.2版本(指Visual Studio 2019)的Boost安装,我们需要下载最新的Boost源代码,并在Visual Studio 2019中编译安装。完成后,我们便可以在项目中使用Boost的功能了。 ### 回答2: Boost是一个开源的C++,提供了许多与C++标准相比还要更强大的功能和工具。MSVC14.2指的是Microsoft Visual C++(MSVC)编译器的版本号。 Boost和MSVC编译器之间的关系是:Boost可以在不同的C++编译器中使用,包括MSVC编译器。而MSVC编译器的版本号14.2表示它属于Visual Studio 2019的一部分,所以在使用Boost时,我们需要根据具体的MSVC版本来选择合适的Boost版本。 对于MSVC 14.2(Visual Studio 2019),可以使用Boost的最新版本。Boost官方网站会提供有关不同版本和编译器之间的兼容性信息。在下载Boost时,我们需要注意选择与我们正在使用的MSVC版本兼容的Boost版本。 在使用Boost之前,我们还需要确保正确配置了MSVC编译器的相关设置。在Visual Studio 2019中使用Boost的过程大致如下: 1. 下载并解压缩Boost的最新版本。 2. 打开Visual Studio 2019,创建一个新项目或打开现有项目。 3. 在项目属性中,选择C/C++ -> 常规,将Boost的include目录添加到附加包含目录中。 4. 在链接器 -> 常规中,将Boost的lib目录添加到附加目录中。 5. 在链接器 -> 输入中,将Boost文件(.lib)添加到附加依赖项中。 6. 在代码中,使用#include <boost/...>来引入所需的Boost头文件,并编写相应的代码。 总之,Boost可以与MSVC14.2(Visual Studio 2019)一起使用。需要注意的是选择与MSVC版本兼容的Boost版本,并正确配置项目属性来使用Boost的功能。 ### 回答3: boostmsvc14.2是指适用于Microsoft Visual Studio 2019的boost版本。boost是一个开源的C++扩展,提供了很多功能强大且易于使用的工具和组件,用于增强C++编程的能力。 msvc14.2表示该boost版本适用于使用Microsoft Visual Studio 2019的C++开发环境。Msvc14.2是VS2019的默认工具集版本号,其中msvc表示Microsoft Visual C++,14.2表示Major版本为14,Minor版本为2。 使用boostmsvc14.2可以获得许多优势。首先,boost提供了丰富的函数、类和模板,用于处理各种常见的编程任务,如字符串处理、容器操作、多线程编程等。这些功能可以帮助开发人员提高开发效率,减少代码量,从而提升软件的质量和性能。 其次,boost具有良好的移植性和跨平台性。无论是在Windows、Linux还是其他操作系统上,boost都可以正常工作,并且提供了一致的API接口,确保了代码的可移植性。 此外,boost还支持最新的C++标准,提供了许多新特性和扩展,可以帮助开发人员更好地利用和实践现代C++编程中的新概念和技术。 总结来说,boostmsvc14.2是一套功能强大且适用于Microsoft Visual Studio 2019的C++扩展,可以为开发人员提供更多可能性和便利性,使得他们能够更轻松地开发出高质量、高性能的应用程序。