spawn本质是stackful协程,基于boost.coroutine2实现栈上下文保存与切换,需严守void返回、yield_context参数、strand串行化、error_code显式检查及栈大小限制等规则。

spawn 本质是 stackful 协程,不是 async_xxx 的语法糖
很多人以为 spawn 就是把 async_read 换成 co_await 那种写法,其实完全不是。Boost.Asio 的 spawn 基于 Boost.Coroutine2(非 C++20 协程),它在栈上保存完整执行上下文,每次挂起/恢复都涉及栈拷贝和切换——这意味着不能在 spawn 启动的函数里直接用局部大对象、递归过深、或跨线程访问未同步的栈变量。
实操建议:
-
spawn启动的函数必须是void返回类型,且参数只能是yield_context或其引用(如boost::asio::yield_context) - 不要试图在
spawn函数里捕获std::exception_ptr后再抛出——栈切换会破坏异常传播链,改用返回错误码或boost::system::error_code - 若需传参,必须通过闭包捕获,且确保被捕获对象的生命周期覆盖整个协程运行期(常见坑:捕获局部
std::string后协程异步延迟执行,字符串已析构)
spawn 必须搭配 io_context::strand 使用才能安全共享状态
多个 spawn 协程并发读写同一个 socket 或共享变量时,Asio 不自动加锁。因为 spawn 是协作式调度,不等同于线程,但也不保证原子性——尤其当协程在不同线程中被唤醒(比如一个 spawn 在 io_context::run() 线程,另一个在 post() 触发的线程)。
实操建议:
- 所有对共享资源(如
std::vector<int></int>缓存、tcp::socket对象)的访问,必须包裹在strand内:用make_strand(io_context)构造,然后以strand.dispatch()或strand.post()调度协程启动 - 别用
io_context::wrap()替代strand——它只保正回调在同一线程执行,不防重入;而strand提供串行化语义,这才是协程间状态安全的关键 - socket 本身不是线程安全的,即使只在一个
spawn里用,也要注意:如果该 socket 同时被另一个线程调用close(),会触发未定义行为——所以关闭动作也得走同一strand
错误处理必须显式检查 error_code,不能依赖异常
Asio 的 spawn 默认不抛异常,所有 I/O 错误都通过 boost::system::error_code& 参数返回。如果你漏掉这个参数,或者写了 error_code ec(值传递)而不是 error_code& ec(引用),就会收不到错误信息,协程静默失败。
常见错误现象:async_read 立即返回 error_code=operation_aborted,但协程继续往下跑,后续操作崩溃。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
实操建议:
- 每个 Asio I/O 操作后必须立刻检查
ec,例如:async_read(socket, buf, ec); if (ec) return; - 不要在
spawn函数里 try/catch ——spawn启动的函数不支持栈展开,异常会直接 terminate - 若需统一错误处理,用
boost::asio::async_compose自定义操作,而非硬塞 try/catch
协程栈大小默认 128KB,超限就 SIGSEGV,但无法 runtime 调整
Boost.Coroutine2 为每个 spawn 分配固定大小栈,默认 128KB。它不支持像 Windows Fiber 或 Linux ucontext 那样动态增长,一旦局部变量 + 调用深度超过限额,就是段错误,且堆栈信息里看不到协程痕迹,只显示 coro_invoke 地址。
性能与兼容性影响:增大栈会提高内存占用(尤其高并发时),减小则容易 crash;而且该值在编译时由 BOOST_COROUTINES_NO_DEPRECATION_WARNING 和宏 BOOST_COROUTINES_STACK_SIZE 控制,运行时不可改。
实操建议:
- 编译前定义
-DBOOST_COROUTINES_STACK_SIZE=262144(256KB)应对复杂逻辑,但别盲目设到 1MB——Asio 协程不是为重型计算设计的 - 避免在
spawn函数里定义大数组(如char buf[65536]),改用std::vector<char>(65536)</char>或堆分配 - 递归调用
spawn是危险操作,每次嵌套都吃一份栈——应改为循环 + 状态机模拟
协程栈和 strand 是最容易被跳过的两个点,但只要其中一个没对齐,程序就可能在压力下随机崩,而且复现困难。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










