在 C++20 标准中,协程(coroutine)被正式纳入语言核心,极大提升了异步编程的便利性和性能。本文将从协程的基本概念、实现细节、关键关键字,到一个实际的网络请求示例,系统阐述如何在现代 C++ 项目中使用协程。
一、协程概念回顾
协程是可挂起的函数,能够在执行过程中暂停(yield)并在之后恢复执行。与传统线程相比,协程是轻量级的,可避免频繁的上下文切换和堆栈分配。协程在底层被实现为状态机,通过编译器自动生成状态机代码,使得写法直观如普通函数。
二、C++20 协程的核心特性
- 关键字
co_await:等待一个 awaitable 对象。co_yield:在协程内部产生一个值,类似生成器。co_return:返回协程最终结果。
- awaitable
协程只能co_await满足await_ready、await_suspend、await_resume这三个成员函数的对象。 - promise_type
每个协程都有一个与之对应的 promise,负责协程返回值、异常传播以及协程生命周期的管理。 - std::coroutine_handle
用于手动管理协程句柄,如promise.promise()返回的句柄可以用来恢复或销毁协程。
三、协程与异步 I/O 的结合
在实际项目中,协程常与异步 I/O 库(如 libuv、Boost.Asio、asio::awaitable)配合,形成高并发网络服务。示例代码:
#include <asio.hpp>
#include <iostream>
asio::awaitable <void> async_echo_server(asio::ip::tcp::socket sock) {
char data[1024];
try {
while (true) {
std::size_t n = co_await sock.async_read_some(asio::buffer(data), asio::use_awaitable);
if (n == 0) break; // 客户端关闭
co_await sock.async_write_some(asio::buffer(data, n), asio::use_awaitable);
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
}
}
int main() {
asio::io_context ctx;
asio::ip::tcp::acceptor acceptor(ctx, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 12345));
ctx.post([&]() mutable {
co_spawn(ctx, [&]() -> asio::awaitable <void> {
while (true) {
auto sock = co_await acceptor.async_accept(asio::use_awaitable);
co_spawn(ctx, async_echo_server(std::move(sock)), asio::detached);
}
}, asio::detached);
});
ctx.run();
}
上述示例使用 Boost.Asio 的 awaitable 适配器,将传统异步回调写法转换为协程代码,阅读与调试更为直观。
四、协程实现原理简析
编译器会把带 co_* 的函数转换为结构体,内部包含状态机实现。关键点在于:
- Suspend/Resume:当遇到
co_await、co_yield时,协程会返回给调用者,记录当前状态;随后再次被调度时,恢复到保存的状态继续执行。 - 内存分配:协程的堆栈(即状态机对象)通常在堆上分配,以避免栈溢出。
std::coroutine_handle用来指向这个对象。 - 异常处理:异常会被封装进 promise 的
unhandled_exception(),调用者可通过await_resume()捕获。
五、协程的性能优势
- 无栈切换:相比线程,协程只需要保存少量上下文(返回地址、寄存器)即可恢复。
- 低延迟:协程在同一线程内运行,避免线程调度开销,适合高频 I/O。
- 可组合性:协程函数可以像普通函数一样被
co_await,实现模块化。
六、常见坑与最佳实践
- 不要在协程中使用阻塞 I/O:否则会导致协程挂起后仍然阻塞线程,失去协程优势。
- 记得使用
asio::detached或co_spawn的返回句柄:若不释放句柄,协程会泄漏资源。 - 合理使用
use_awaitable:它是 Asio 协程适配器,能让async_*成为 awaitable。 - 异常安全:在 promise 的
unhandled_exception()中记录或抛出,确保协程异常不会导致程序崩溃。
七、总结
C++20 协程为异步编程提供了语言级别的简洁语法,降低了回调地狱的概率。通过结合成熟的异步 I/O 库(如 Boost.Asio),开发者可以在保持高性能的同时,编写出更易读、易维护的网络服务。随着标准库的进一步完善,协程将在大规模并发、高性能服务器等领域得到更广泛应用。