Boost Asio中strand与C++20协程段错误问题及正确用法咨询
我刚接触Asio,正在理解strand的概念,虽已通过文档了解其理论,但在使用中遇到问题。我认为若有需要同步访问的资源,可将其与strand绑定,确保操作资源时Asio在该strand中执行。
我尝试编写如下协程循环:
asio::awaitable<void> progress(auto strand) { int i = 0; for (;;) { co_await dispatch(bind_executor(strand, asio::deferred)); ore::util::print("Strand Executor 1:", ++i); // ...对同步资源执行操作... co_await dispatch(asio::deferred); ore::util::print("回到IO上下文执行器"); // ...执行不依赖同步资源的代码... co_await dispatch(bind_executor(strand, asio::deferred)); ore::util::print("Strand执行器2"); // ...再次执行依赖同步资源的代码... } } int main() { asio::io_context io; auto my_strand = make_strand(io); co_spawn(io, progress(my_strand), asio::detached); io.run(); }
当strand基于asio::io_context创建时,程序在约2600次迭代后出现段错误:
... T0 Strand Executor 1: 2657 T0 回到IO上下文执行器 Segmentation fault (core dumped)
若strand基于独立线程池创建则无此问题:
asio::thread_pool tp(1); auto my_strand = make_strand(tp);
甚至更简单的测试代码也会触发段错误:
asio::awaitable<void> progress() { for (;;) { co_await dispatch(asio::deferred); } } int main() { asio::io_context io; co_spawn(io, progress(), asio::detached); io.run(); }
我猜测段错误可能与栈式协程实现有关,但不确定。现提出三个问题:
- 为何会出现段错误?
- 为何基于独立执行器创建strand时无段错误?
- 这种strand的设计与使用方式是否正确?
1. 段错误原因
这是Asio栈式协程的栈溢出问题。当你使用单线程运行的asio::io_context时,协程每次co_await dispatch(asio::deferred)都会在同一个线程的调用栈上直接恢复执行。由于是无限循环,调用栈会不断累积栈帧,每次恢复协程都会增加栈的使用量,直到超过系统栈的大小限制,最终触发段错误。
dispatch搭配deferred在单线程IO上下文场景下,会直接在当前调用栈内恢复协程,不会将任务放入队列后重新调度,导致栈空间被持续消耗直至溢出。
2. 独立线程池无段错误的原因
当strand基于asio::thread_pool(哪怕是1线程的池)创建时,dispatch操作会将协程恢复任务放入线程池的任务队列。线程池的工作线程在处理任务时,会从自身线程栈开始执行协程恢复,每次恢复都是在“干净”的栈帧上启动,不会在之前的调用栈上累积栈帧,因此不会出现栈溢出。
3. Strand的设计与使用方式是否正确?
你的核心思路是对的:将需要同步访问的资源与strand绑定,通过strand序列化对资源的操作,这是Asio中保护共享资源的标准做法。
但代码中的协程循环方式存在问题:
- 无限循环中频繁切换执行器且无任何延迟,会导致CPU占用过高,同时在单线程IO上下文场景下触发栈溢出。
- 若需周期性执行任务,应使用
asio::steady_timer搭配协程,既可以避免栈溢出,也能控制执行频率。
优化后的示例代码(使用定时器):
asio::awaitable<void> progress(auto strand) { int i = 0; asio::steady_timer timer(co_await asio::this_coro::executor); for (;;) { timer.expires_after(std::chrono::milliseconds(100)); co_await timer.async_wait(asio::deferred); co_await dispatch(bind_executor(strand, asio::deferred)); ore::util::print("Strand Executor 1:", ++i); // 操作共享资源 co_await dispatch(asio::deferred); ore::util::print("回到IO上下文执行器"); // 执行不依赖共享资源的操作 co_await dispatch(bind_executor(strand, asio::deferred)); ore::util::print("Strand执行器2"); // 再次操作共享资源 } } int main() { asio::io_context io; auto my_strand = make_strand(io); co_spawn(io, progress(my_strand), asio::detached); io.run(); }
若确实需要无延迟的循环(不推荐,除非有特殊需求),可以用asio::post替代dispatch,或者使用多线程IO上下文,确保协程恢复时切换到新的栈帧。
内容的提问来源于stack exchange,提问作者oreganop

