协程是 C++20 标准引入的一项强大功能,它让我们可以用同步代码的写法来处理异步流程。相比传统的回调或线程池,协程在性能、可读性和错误处理方面都有显著优势。下面,我们从基础概念、关键字、实现细节和实际使用场景四个方面,对 C++20 协程进行全面拆解,帮助你快速上手。
1. 协程基础概念
| 概念 | 解释 |
|---|---|
| 协程函数 | 带 co_await、co_yield、co_return 的普通 C++ 函数。 |
| Suspension Point | 代码执行被挂起的位置,常见的有 co_await、co_yield。 |
| Awaitable | 可以被 co_await 的对象。实现了 await_ready()、await_suspend()、await_resume() 三个成员函数。 |
| Promise | 协程的上下文,用于保存返回值、异常等。 |
| Coroutine Handle | 对协程的句柄,能够启动、挂起、销毁协程。 |
协程的执行流程:
- 创建:编译器为协程生成一个状态机结构,自动创建
promise_type。 - 开始:调用
handle.resume()或者在协程入口自动调用。 - 挂起:遇到
co_await、co_yield,执行await_suspend(),返回true则挂起,否则直接继续。 - 恢复:调用
handle.resume(),继续执行到下一个挂起点。 - 结束:执行到
co_return或者抛出异常,await_resume()被调用完成整个协程。
2. 关键字与核心 API
| 关键字 | 用途 |
|---|---|
co_await |
等待一个 Awaitable 对象的完成。 |
co_yield |
在生成器函数中产生一个值,返回给调用者。 |
co_return |
结束协程并返回一个值(若存在)。 |
2.1 co_await 细节
co_await 需要待等待对象满足 Awaitable 接口:
struct MyAwaitable {
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<>) noexcept;
int await_resume() noexcept { return 42; }
};
await_ready():如果返回true,协程立即继续执行,不会挂起。await_suspend(handle):挂起点,决定协程是否真正挂起。可以在此把协程 handle 存到某个事件系统中。await_resume():协程恢复后,co_await表达式的结果。
2.2 co_yield 与生成器
生成器函数返回 `std::generator
`(需要 “ 或第三方实现)。示例: “`cpp std::generator count(int n) { for (int i = 0; i #include #include #include #include struct FileReadAwaitable { asio::ip::tcp::socket& socket_; std::vector buffer_; std::size_t bytes_read_ = 0; bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle h) noexcept { socket_.async_read_some( asio::buffer(buffer_), [h](std::error_code ec, std::size_t n){ // 这里可以捕获错误 h.resume(); }); } std::size_t await_resume() noexcept { return bytes_read_; } }; struct AwaitableFileReader { asio::ip::tcp::socket socket_; std::vector buffer_{1024}; AwaitableFileReader(asio::io_context& ctx, const std::string& host, const std::string& port) : socket_(ctx) { // 简化示例:直接连接 asio::ip::tcp::resolver resolver(ctx); auto endpoints = resolver.resolve(host, port); asio::connect(socket_, endpoints); } std::size_t operator()(std::vector & out) { FileReadAwaitable awaitable{socket_, buffer_}; std::size_t n = co_await awaitable; out.assign(buffer_.begin(), buffer_.begin() + n); co_return n; } }; “` 使用方式: “`cpp asio::io_context ctx; AwaitableFileReader reader(ctx, “example.com”, “80”); std::vector data; auto task = reader(data); ctx.run(); “` — ## 4. 协程的性能优势 | 传统异步方案 | 协程方案 | |————–|———-| | 回调层层嵌套 | 线性代码 | | 线程上下文切换 | 无上下文切换 | | 难以捕获异常 | `try/catch` 直接作用于协程体 | | 资源管理复杂 | `co_return` 与 RAII 结合简洁 | 实验表明,使用协程进行网络 I/O 或文件 I/O 时,吞吐量提升 20%~30%,CPU 使用率下降 10%~15%。 — ## 5. 注意事项与常见坑 1. **Awaitable 必须满足标准**:忘记实现 `await_suspend` 会导致编译错误。 2. **协程对象生命周期**:不要在协程内部持有局部变量的引用,导致悬空引用。 3. **异常传递**:异常会直接抛到协程的调用者,需要在调用点使用 `try/catch`。 4. **内存占用**:每个协程都要存一个状态机,若并发数极高,需使用 `std::async` 或线程池做分流。 5. **与第三方库兼容**:部分库(如 Boost.Asio)已有协程包装,直接使用即可。 — ## 6. 结语 C++20 的协程为我们提供了一种全新的异步编程范式,让复杂的异步逻辑变得像同步代码一样直观。掌握 `co_await`、`co_yield` 与 `co_return` 的使用,以及 Awaitable 接口的实现,能够让你在网络、文件、数据库等多种场景中高效编写可维护、可扩展的异步程序。希望本文能成为你踏入协程世界的起点,祝编码愉快!