协程调度器必须用 io_context 配合 co_await 才能真正挂起 I/O 操作,因 C++20 协程本身不感知 I/O,需依赖如 Boost.Asio 的 io_context 提供可挂起的 awaitable 对象,否则 co_await 仅立即返回,沦为伪异步。

协程调度器必须用 io_context 配合 co_await 才能真正挂起 I/O 操作
很多人误以为只要用了 std::coroutine_handle 就能调度异步 I/O,其实不然。C++20 协程本身不感知 I/O,必须依赖底层执行上下文(如 Boost.Asio 的 io_context 或 libc++ 的实验性 std::experimental::net)提供可挂起的 awaitable 对象。否则 co_await 只是立即返回,变成伪异步。
实操建议:
- 优先选用 Boost.Asio 1.78+,它提供了完整支持
co_await的 socket、timer、executor 等 awaitable 类型 - 不要自己封装裸
poll/epoll+ 协程——缺少 awaitable 语义,无法触发挂起/恢复,调度器会忙等 - 每个
io_context实例需配一个或多个工作线程(io_context::run()),协程会在其关联的 executor 上恢复 - 若用
std::jthread启动io_context::run(),注意捕获异常并调用io_context::stop()防止阻塞退出
steady_timer 是定时器引擎唯一可靠选择,system_timer 在系统时间跳变时会出错
定时任务若依赖绝对时间(比如“每天 9:00 执行”),才考虑 system_timer;但绝大多数调度场景(间隔轮询、超时控制、延迟投递)必须用 steady_timer,它基于单调时钟,不受 NTP 调整、手动改系统时间影响。
常见错误现象:system_timer 在服务器时间被运维回拨后,所有 pending 定时器突然批量触发,或无限延迟不触发。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 创建定时器时显式绑定 executor:
steady_timer timer(ioc.get_executor(), 500ms) - 重复定时任务不要循环
co_await timer.async_wait(...),而应在 await 回调里重置:timer.expires_after(500ms); co_await timer.async_wait(); - 避免在定时器回调中直接调用
timer.cancel()—— 可能引发竞态;应使用error_code ec; timer.cancel(ec);并检查ec == errc::operation_canceled
轻量级调度器的关键不是协程数量,而是避免跨线程切换和内存分配
协程栈默认在堆上分配(除非用 std::coroutine_traits<>::promise_type 自定义 allocator),频繁创建/销毁协程会触发大量小内存分配,比线程切换开销还大。真正的“轻量”来自复用与零拷贝。
实操建议:
- 为高频任务(如连接心跳)预分配协程池,用
std::pmr::monotonic_buffer_resource配合自定义 promise 分配器 - 禁止在协程 resume 路径中调用
new、std::string构造、或任何可能抛异常的函数——异常会穿透协程帧,导致未定义行为 - 若需跨 executor 调度(如从 I/O 线程切到计算线程),用
post(executor, [&](){ co_await ... })显式转移,而不是直接co_await切换 - 慎用
co_yield做协作式调度——它不释放 CPU,仅让出当前协程执行权,容易造成饥饿
调试协程调度失败时,先检查 error_code 和 io_context::stopped()
协程挂起后没恢复,90% 是因为底层操作失败但被忽略,或者 io_context 已停止运行。编译期无法发现,运行时表现为“卡死”或“静默丢任务”。
最容易被忽略的地方:协程中 co_await socket.async_read_some(...) 失败时,error_code 不为 errc::success,但若没显式处理,协程会继续往下执行,后续逻辑可能崩溃或误判。
实操建议:
- 所有
co_awaitI/O 操作后立刻检查:if (ec) { /* 记录日志并 return */ } - 在
io_context::run()循环外加守卫:while (!ioc.stopped()) { ioc.restart(); ioc.run_one(); },防止 stop 后再 run 导致无响应 - 用
asio::detached或asio::redirect_error替代裸co_await,便于统一错误注入和测试
协程调度器最难的部分不是写 co_await,而是确保每个 awaitable 对象真正挂起、每个错误路径都可控、每个定时器行为可预测——这些细节不在语法里,而在 executor 绑定、error_code 处理和时钟选择中。


















