掌握C++20协程的使用技巧

C++20 引入了协程(coroutines),它们让我们可以写出更直观、更高效的异步代码。协程本质上是一种可以挂起和恢复的函数,使用 co_yieldco_awaitco_return 等关键字来控制执行流程。下面从基础语法、实现细节、性能收益、常见陷阱以及实战案例五个方面展开讨论。

1. 协程的基本构成

// 简单的生成器
generator <int> simple_gen(int n) {
    for (int i = 0; i < n; ++i) co_yield i;   // 挂起并返回当前值
}
  • `generator ` 是一个模板,内部维护了状态机。
  • co_yield 表示将值返回给调用者,并挂起协程。
  • co_return 用于返回终值,结束协程。

coroutine_handle

协程运行时会产生一个 coroutine_handle,用于控制协程生命周期(挂起、恢复、销毁)。标准库提供了 `std::coroutine_handle

`。如果你需要手动管理协程,可直接操作该句柄。 ## 2. Promise 与 Awaiter 协程的底层机制是 **Promise** 与 **Awaiter**。Promise 用来在协程开始、返回、异常时保存状态;Awaiter 则定义了 `await_ready`、`await_suspend`、`await_resume` 三个函数,决定协程是否需要挂起。 “`cpp struct Awaitable { bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) { /* 触发异步事件 */ } int await_resume() const noexcept { return 42; } }; “` `co_await Awaitable{}` 会在 `await_suspend` 中挂起协程,等事件完成后再恢复。 ## 3. 性能收益与成本 – **内存占用**:协程的状态机会占用一定堆内存,默认是 stackless 形式,若需要更大局部状态可以使用 `co_await` 结合 `std::promise` 或 `std::future`。 – **调度开销**:协程的挂起恢复由编译器生成的状态机完成,开销远小于线程上下文切换。 – **与异步 I/O**:在网络或文件 I/O 上,协程与事件循环配合可实现类似 `async/await` 的写法,降低回调地狱。 ## 4. 常见陷阱 | 现象 | 说明 | 解决方案 | |——|——|———-| | 协程抛异常导致程序崩溃 | Promise 默认不捕获异常 | 在 `promise_type` 中实现 `void unhandled_exception()` | | 内存泄漏 | 未手动销毁 `coroutine_handle` | 使用 `co_return` 或 `co_await` 时让 `await_suspend` 负责销毁 | | 线程安全 | 协程不等价于线程 | 使用 `std::atomic` 或互斥锁保护共享状态 | ## 5. 实战案例:异步文件读取 假设我们使用 libuv 或 ASIO 实现异步文件读取,下面给出一个简化示例,演示如何把异步回调包装成可协程的 `Awaitable`。 “`cpp #include #include #include #include #include struct AsyncRead { std::string filename; std::vector buffer; struct Awaiter { AsyncRead* self; bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) { // 简化:同步读取文件,实际场景中会调用异步 API std::ifstream f(self->filename, std::ios::binary); if (f) { f.seekg(0, std::ios::end); size_t size = f.tellg(); f.seekg(0); self->buffer.resize(size); f.read(self->buffer.data(), size); } h.resume(); // 读取完成后立即恢复 } std::vector await_resume() noexcept { return std::move(self->buffer); } }; Awaiter operator co_await() { return Awaiter{this}; } }; async_auto read_file(std::string name) -> std::vector { AsyncRead ar{std::move(name)}; co_return co_await ar; } int main() { auto task = read_file(“example.txt”); std::string content(task.begin(), task.end()); std::cout

发表评论