协程替代回调需确保co_await作用于合法awaitable对象,缓冲区须持久有效,错误处理需兼顾异常与error_code,executor和线程安全须显式管控,栈开销较大需压测优化。

协程替代回调时,co_await 必须作用于可挂起对象
传统回调代码里,网络读写分散在多个函数中,状态靠成员变量或闭包捕获维护;协程改写后,逻辑变线性,但前提是每个 co_await 后面跟的是真正支持挂起的 awaitable 类型——不是随便封装个 std::function 就能用。
常见错误现象:co_await async_read(socket, buf) 编译失败,报错类似 'await_ready' is not a member of 'xxx',说明你传进去的不是合法 awaitable。
- 必须确保底层 I/O 封装返回类型实现了
await_ready()、await_suspend()、await_resume()三者(C++20) - 别直接
co_await std::async(...)——std::future默认不可等待,需用std::experimental::future或自行适配(如 libunifex 的as_awaitable) - libuv、boost.asio 1.70+ 提供了原生协程支持,比如
asio::use_awaitable是正确起点,不是自己造轮子
asio 协程中 co_await socket.async_read_some(..., use_awaitable) 的参数陷阱
用 asio 改写时,最常卡在参数顺序和缓冲区生命周期上:协程栈上的 std::array 或局部 std::vector 缓冲区,在挂起后可能已被析构,导致读到野内存。
- 缓冲区必须保证在协程恢复前有效 —— 推荐用
asio::streambuf或堆分配的std::vector(配合shared_ptr管理) -
async_read_some不保证读满,要循环co_await直到满足长度,或改用asio::async_read(它内部处理分片) - 错误处理不能只靠
co_await抛异常:需显式检查std::error_code,因为某些场景(如连接关闭)会走 error path 而不抛异常
示例片段:
立即学习“C++免费学习笔记(深入)”;
auto buf = std::make_shared<std::vector<char>>(1024);
co_await socket.async_read_some(
asio::buffer(*buf), asio::use_awaitable);
// buf 在协程挂起期间仍有效
从回调切换到协程后,executor 切换和线程安全容易被忽略
回调风格通常依赖 asio 的 post() 显式切 executor;协程看似“自动”,其实每次 co_await 恢复的位置,由 await_suspend 返回的 handle 决定——它可能把恢复调度到别的线程,也可能就地执行。
- 若原回调逻辑依赖单线程串行(如共享状态无锁访问),协程必须绑定到同一
asio::io_context::strand,否则并发修改会出问题 - 不要假设
co_await use_awaitable总是回到原线程:默认行为取决于 executor 的dispatch()/post()实现,strand 之外的 executor 可能跨线程 - 调试时加日志打印
std::this_thread::get_id(),确认恢复点是否符合预期
协程栈帧比回调闭包更重,高频短连接下内存和性能要注意
每个协程实例都带独立栈(默认 1MB,可调),而回调只是函数指针+捕获数据。对每秒数千连接的服务器,协程数量暴增时,栈内存和调度开销会明显高于回调。
- 避免在协程内做大量局部变量分配;优先复用 buffer、用
std::span替代拷贝 - 考虑协程池或栈复用机制(如 asio 的
awaitable_thread_pool),但注意引入额外同步成本 - 压测时对比 RSS 和协程创建/销毁频率,
valgrind --tool=massif或perf record -e task-clock更有用,而不是只看吞吐
协程不是银弹,它让逻辑变清晰,但把调度、内存、线程模型这些隐含约束全摊开了——没处理好,反而比回调更难 debug。








