strand 是一种调度序号标记与串行化队列机制,能保证同一 strand 提交的回调按 FIFO 顺序串行执行,但需始终通过 bind_executor/wrap 等方式提交 handler,不可绕过直接调用。

strand 是什么,它真能保证回调串行吗
strand 不是锁,也不是线程绑定机制。它本质是一个调度序号标记 + 串行化队列:所有通过同一个 strand 提交的 handler(比如 async_read 的回调),会被强制排进一个 FIFO 队列,由底层 io_context::run() 或显式调用 dispatch()/post() 时按序执行。前提是——你不能绕过它直接调用 handler。
常见错误现象:std::thread 中直接调用本该走 strand 的函数,或在 strand 外部捕获并保存 handler 后手动调用,都会导致竞态。
- 必须用
strand.wrap()或boost::asio::bind_executor(strand, handler)包裹 handler - 所有共享状态的访问,只应在被
strand调度的 handler 内发生 -
strand对象本身可拷贝(内部是引用计数),但多个拷贝指向同一串行上下文
如何正确构造和使用 strand 实例
strand 必须绑定到某个 io_context,且生命周期不能短于它所服务的所有异步操作。
boost::asio::io_context io;
auto strand = boost::asio::make_strand(io); // 推荐:C++17 起可用
// 或旧写法:boost::asio::io_context::strand strand{io};
关键点:
- 不要对同一个
io_context创建大量独立strand实例来“隔离逻辑”——这不会提升并发性,反而增加调度开销 - 若需逻辑分组(如 per-connection 串行),每个 connection 持有一个
strand即可,别跨 connection 复用 -
strand构造后立即可用,但若io_context已停止运行,post()会静默丢弃 handler(不报错)
async_xxx 调用时怎么把回调塞进 strand
不是“注册 strand”,而是每次提交异步操作时,显式指定 executor。Boost.Asio 从 1.70 开始统一用 executor 抽象,strand 就是一种 executor。
boost::asio::ip::tcp::socket sock{io};
auto strand = boost::asio::make_strand(io);
// 错误:回调会在任意线程中执行(可能并发)
sock.async_read_some(buffer, [](auto ec, auto n) { /* ... */ });
// 正确:绑定到 strand
sock.async_read_some(buffer,
boost::asio::bind_executor(strand,
[](auto ec, auto n) { /* 安全:必在 strand 序列中执行 */ })
);
其他等效写法:
-
strand.wrap(handler):返回一个新 handler,内部已绑定 -
boost::asio::dispatch(strand, handler):立即尝试内联执行(若当前线程正持有该 strand) -
boost::asio::post(strand, handler):总是入队,不内联
注意:dispatch() 在非 strand 线程中调用是安全的,但若已在该 strand 上下文中,可能递归调用,需防栈溢出。
容易被忽略的坑:strand 不保护 shared_ptr 成员、不解决数据竞争根源
strand 只保证 handler 调用顺序,不自动管理对象生命周期,也不阻止你在 handler 外部修改共享数据。
典型翻车场景:
- 类成员是
std::shared_ptr,但T的方法没加任何同步,且被多个不同strand(或无 strand)调用 → 仍数据竞争 - 在
strandhandler 中修改了某全局 map,但另一个线程通过定时器(未绑 strand)也在读写它 → 竞态照旧 -
strand被销毁后,仍有 pending handler 在队列里 → 行为未定义(通常 crash)
所以:
- 所有被多个异步路径访问的资源,要么彻底限定在单个
strand内操作,要么额外加std::mutex(仅当必须跨 strand 共享时) - 用
weak_ptr配合strand做对象存活检查,避免 handler 执行时对象已析构 -
strand生命周期应与它服务的 socket / session 对象一致,通常作为类成员持有
多线程下真正安全的串行,靠的不是“用了 strand”,而是“所有对某块状态的访问,都收敛到同一个 executor 序列里”。漏掉一次直连调用,整个防线就垮了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











