如何从th2线程向th1线程启动Boost fiber并加入其调度队列
问题根因
Boost.Fiber 是线程绑定的协程库:每个线程的调度器实例、fiber 队列都是线程本地存储(TLS)隔离的,你在 th2 里直接调用创建 fiber 的代码,新 fiber 会被绑定到 th2 的调度器上下文——但你根本没在 th2 跑 fiber 调度循环,就算你给 th1 的调度器加了锁也没用,因为创建 fiber 时压根不会跨线程往 th1 的队列里塞东西。
另外你代码里每次调用launchFiber都执行boost::fibers::use_scheduling_algorithm< priority_scheduler >()是错误用法,这个接口只需要在对应线程启动fiber调度循环前调用一次,重复调用会重复初始化TLS上的调度器实例,直接触发未定义行为。
正确实现方案
要实现从 th2 往 th1 投递 fiber,核心是用 Boost.Fiber 内置的线程安全通道做跨线程任务传递,不要自己跨线程直接操作调度器队列:
- 首先在 th1 初始化调度器的时候,绑定一个跨线程可访问的无锁通道,专门用来接收外部线程提交的fiber任务
- th2 不需要直接创建绑定到th1的fiber,而是把待执行的fiber逻辑、优先级、名称打包,通过通道投递到th1
- th1的调度器在每次调度轮转时,先拉取通道里的待启动任务,在th1本地上下文创建fiber后加入就绪队列,再参与优先级调度
可运行代码示例
首先定义优先级属性、跨线程投递通道和适配后的优先级调度器:
#include <boost/fiber/all.hpp> #include <mutex> #include <queue> #include <memory> #include <functional> #include <tuple> // 原有优先级属性定义 struct priority_props : public boost::fibers::fiber_properties { int priority = 0; std::string name; priority_props(boost::fibers::fiber_properties::backptr_t bp) : boost::fibers::fiber_properties(bp) {} void set_priority(int p) { if (p != priority) { priority = p; notify(); } } }; // 全局跨线程投递通道:th2写、th1读,Boost.Fiber自带的通道原生线程安全 using pending_fiber_pkg = std::tuple<std::function<void()>, int, std::string>; boost::fibers::unbounded_channel<pending_fiber_pkg> g_fiber_submit_chan; // 修正后的优先级调度器 class priority_scheduler { private: mutable std::mutex mtx_; // 就绪队列:存上下文指针、优先级、fiber名 std::queue<std::tuple<boost::fibers::context*, int, std::string>> ready_queue_; boost::fibers::context* main_ctx_ = nullptr; public: priority_scheduler() = default; priority_scheduler(const priority_scheduler&) = delete; priority_scheduler& operator=(const priority_scheduler&) = delete; // 调度器必填接口:新fiber创建完成时的唤醒回调 void awakened(boost::fibers::context* ctx, boost::fibers::fiber_properties props) noexcept { auto& p = props.get<priority_props>(); std::lock_guard<std::mutex> lk(mtx_); ready_queue_.emplace(ctx, p.priority, p.name); } // 调度器必填接口:选择下一个要运行的fiber boost::fibers::context* pick_next() noexcept { // 先拉取所有外部线程投递的待创建任务 pending_fiber_pkg pkg; while (g_fiber_submit_chan.try_pop(pkg)) { auto [func, prio, name] = std::move(pkg); // 注意:这里是在th1上下文创建fiber,会自动绑定到th1的调度器 boost::fibers::fiber f(boost::fibers::launch::post, std::move(func)); auto& props = f.properties<priority_props>(); props.set_priority(prio); props.name = std::move(name); f.detach(); } std::lock_guard<std::mutex> lk(mtx_); if (ready_queue_.empty()) { return main_ctx_; } // 这里简化逻辑直接取队首,你可以自行实现按优先级排序取最高优先级任务的逻辑 auto [ctx, prio, name] = ready_queue_.front(); ready_queue_.pop(); return ctx; } // 调度器必填接口:判断是否存在待运行fiber bool has_ready_fibers() const noexcept { std::lock_guard<std::mutex> lk(mtx_); // 必须把通道里待投递的任务算进去,否则调度循环会提前退出 return !ready_queue_.empty() || !g_fiber_submit_chan.empty(); } // 调度器必填接口:绑定线程主上下文 void set_main_ctx(boost::fibers::context* ctx) noexcept { main_ctx_ = ctx; } // 调度器绑定的属性类型声明 using properties_t = priority_props; };
th1线程的初始化和调度循环逻辑:
void th1_entry() { // 仅在th1启动时设置一次调度器,禁止重复调用 boost::fibers::use_scheduling_algorithm<priority_scheduler>(); // 在这里启动th1原有的常驻业务fiber即可 // 例:boost::fibers::fiber(original_th1_fiber_func).detach(); // th1调度主循环 while (true) { // 没有就绪任务也没有待投递任务时,阻塞等待新任务避免CPU空转 if (!boost::this_fiber::has_ready_fibers() && g_fiber_submit_chan.empty()) { pending_fiber_pkg pkg; g_fiber_submit_chan.pop(pkg); // 阻塞直到th2投递新任务 g_fiber_submit_chan.push(std::move(pkg)); // 塞回通道,下一轮调度会处理 } // 让出执行权给调度器,切换到下一个就绪fiber运行 boost::this_fiber::yield(); } }
跨线程安全的fiber启动接口,th2直接调用即可:
// 任意线程(包括th2)调用该接口,都可以把fiber投递到th1调度运行 void launchFiber(std::function<void()> fiber_func, int priority, std::string fiber_name) { g_fiber_submit_chan.push({ std::move(fiber_func), priority, std::move(fiber_name) }); }
注意事项
- 所有需要绑定到th1调度器的fiber,必须在th1的线程上下文中创建,绝对不要在th2里直接构造
boost::fibers::fiber实例,否则会自动绑定到th2的TLS调度器,永远不会被th1调度 use_scheduling_algorithm仅能在对应调度线程启动时调用一次,重复调用会破坏TLS上的调度器状态- 直接使用Boost.Fiber自带的
unbounded_channel做跨线程传递即可,它本身是无锁线程安全的,不需要额外加锁 - 调度器的
has_ready_fibers判断必须覆盖通道中的待投递任务,否则th1调度循环会在本地队列为空时直接退出,不会等待th2的新任务
内容的提问来源于stack exchange,提问作者glaird
相关产品推荐
相关产品推荐

