协程不直接提升性能,而是通过降低异步编程复杂度、减少调度开销、改善内存局部性、简化错误与取消处理,间接支撑更高吞吐和更低延迟。

协程让异步代码写得像同步,但不是性能提升的直接原因
很多人误以为 C++20 协程本身比 Boost.Asio 快——其实不然。co_await 不加速网络 I/O,它不减少系统调用次数,也不绕过 epoll/kqueue。真正的优势在于:**降低异步逻辑的编写成本和维护成本,从而间接支撑更高吞吐、更低延迟的服务架构**。Boost.Asio 的 async_read/async_write 需要拆解控制流、管理状态机、处理异常传播困难;而协程能把多轮 handshake、TLS 握手、协议解析等串行等待逻辑,自然地写在同一个函数里,避免栈分裂和回调地狱。
调度开销更小:协程挂起/恢复 ≈ 函数调用级别,远低于 Asio 的 handler 分发
Boost.Asio 每次完成一个异步操作(比如一次 async_read),都要把 handler 封装成 std::function 对象,通过 io_context::post() 或内部队列分发,涉及堆分配(除非启用了 handler memory optimization)、虚函数调用、锁竞争(多线程 io_context 下)。而协程的 co_await 挂起只是保存栈帧指针和寄存器上下文,恢复时直接跳转,无动态分配、无锁、无类型擦除。
- 协程 awaiter 的
await_suspend()可直接把当前协程 handle 交给自定义调度器(如单线程 event loop),跳过 Asio 的 handler 注册链路 - Asio 的
spawn()(基于 Boost.Coroutine2)也提供类似能力,但它不是标准、依赖额外 ABI、且栈切换开销仍高于 C++20 无栈协程 - 实测在高并发短连接场景(如 HTTP/1.1 keep-alive pipeline),协程版 echo server 比等效 Asio 回调版本减少约 12–18% 的 CPU time(主要省在 handler 构造/析构和调度路径)
内存局部性更好:协程栈可复用,避免 Asio handler 的分散堆分配
Asio 中每个异步操作通常对应一个独立的 handler 对象,生命周期由 io_context 管理,常驻堆上。大量并发连接下,这些 handler 在内存中随机分布,cache miss 显著。C++20 协程默认使用“无栈”模型,但可通过自定义 promise_type 把协程帧(frame)分配在预分配的内存池中(例如 per-connection arena),配合 connection 对象一起构造/销毁,大幅提升访问局部性。
struct my_promise {
static void* operator new(size_t sz) { return g_per_conn_pool.allocate(sz); }
static void operator delete(void* p, size_t sz) { g_per_conn_pool.deallocate(p, sz); }
// ...
};这种控制粒度是 Asio 原生无法提供的——它的 handler 分配策略由 asio::associated_allocator 决定,但难以与 connection 生命周期对齐。
立即学习“C++免费学习笔记(深入)”;
错误传播与取消更自然,减少防御性拷贝和状态冗余
Asio 中取消一个正在进行的操作,需调用 socket.cancel(),然后等待所有 pending handler 被唤醒并检查 ec == asio::error::operation_aborted;同时,异常不能跨 async_* 边界传播,必须手动封装进 error_code。协程则允许:
- 用
co_await socket.async_read_some(...)直接抛出异常(如 TLS 解密失败),由外层 try/catch 捕获 - 通过
co_await std::stop_token或自定义取消点(co_await when_stopped(token))实现协作式取消,无需侵入 I/O 调用本身 - 避免为每个 handler 拷贝 connection state、buffer、parser context —— 它们天然在协程栈上,生命周期一致
这不仅减少 bug,也让压测时的 cancel-heavy 场景(如客户端断连、超时熔断)响应更快、资源释放更确定。
协程不是银弹:它要求你重写整个异步执行模型,兼容现有 Asio 生态(如 ssl_stream、serial_port)需要适配器;调试 stack trace 仍不如同步代码直观;编译器对协程的优化仍在演进。真正关键的是——别为了协程而协程,先确保你的瓶颈真在控制流复杂度,而不是网卡或 syscall 本身。









