C++20 中的协程(Coroutines):语法与实战

在 C++20 之前,协程的实现通常依赖于第三方库或手写生成器(如 Boost.Coroutine)。C++20 标准直接将协程语言特性纳入编译器,简化了异步编程和生成器的实现。本文将从协程的基本概念、关键语法、实现细节以及实际应用场景三方面,系统阐述 C++20 协程的使用方法。

1. 协程概念回顾

协程是一种比线程更轻量级的同步机制,允许函数在执行过程中挂起(suspend)并在之后恢复。协程的核心特性是:

  • 挂起点co_awaitco_yieldco_return
  • 协程句柄std::coroutine_handle
  • 协程体co_* 关键字嵌入的函数体)
  • 协程的状态机(编译器自动生成)

协程的执行流程类似于生成器,但可更灵活地处理异步 IO、事件循环等复杂逻辑。

2. C++20 协程的语法

2.1 协程函数声明

协程函数必须返回一个具有 std::suspend_alwaysstd::suspend_never 等协程特性的类型,最常见的是自定义的 promise_type。标准库中提供了 `std::generator

`(C++23),但 C++20 仅提供了 `std::coroutine_handle`。 “`cpp #include #include struct MyCoroutine { struct promise_type { MyCoroutine get_return_object() { return MyCoroutine{std::coroutine_handle ::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handle handle; MyCoroutine(std::coroutine_handle h) : handle(h) {} ~MyCoroutine() { if (handle) handle.destroy(); } }; MyCoroutine example() { std::cout << "Before suspend\n"; co_await std::suspend_always{}; std::cout << "After suspend\n"; } “` ### 2.2 `co_await`、`co_yield` 与 `co_return` – `co_await`:等待一个可 `awaitable` 的对象完成。它可以是一个 `std::future`, `std::promise`, 或自定义的 `awaitable` 结构。 – `co_yield`:生成一个值,类似 `yield` 的行为。协程返回一个迭代器,外部通过 `co_yield` 获取值。 – `co_return`:结束协程并返回值(若函数返回值非 `void`)。 ## 3. 实现自定义 `awaitable` 下面演示一个简易的异步延时协程。 “`cpp #include #include #include struct Sleep { std::chrono::milliseconds duration; struct awaiter { Sleep& self; bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) noexcept { std::thread([self = self.duration, h]() { std::this_thread::sleep_for(self); h.resume(); }).detach(); } void await_resume() const noexcept {} }; auto operator co_await() noexcept { return awaiter{*this}; } }; std::coroutine_handle demo() { std::cout << "Start\n"; co_await Sleep{std::chrono::milliseconds(500)}; std::cout << "Half a second later\n"; co_await Sleep{std::chrono::milliseconds(500)}; std::cout << "Another half a second later\n"; } “` 上述代码展示了如何让协程在后台线程中等待一段时间后恢复执行。需要注意的是,协程的挂起点可以跨线程完成。 ## 4. 生成器示例:斐波那契序列 “`cpp #include #include template struct Generator { struct promise_type { T current_value; std::suspend_always yield_value(T value) { current_value = value; return {}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } Generator get_return_object() { return Generator{std::coroutine_handle ::from_promise(*this)}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handle handle; Generator(std::coroutine_handle h) : handle(h) {} ~Generator() { if (handle) handle.destroy(); } struct Iterator { std::coroutine_handle handle; bool operator!=(std::default_sentinel_t) { return !handle.done(); } Iterator& operator++() { handle.resume(); return *this; } T operator*() const { return handle.promise().current_value; } }; Iterator begin() { handle.resume(); return Iterator{handle}; } std::default_sentinel_t end() { return {}; } }; Generator fibonacci(int n) { int a = 0, b = 1; for (int i = 0; i < n; ++i) { co_yield a; int tmp = a; a = b; b = tmp + b; } } int main() { for (auto val : fibonacci(10)) std::cout << val << ' '; } “` 该生成器示例演示了如何利用 `co_yield` 返回一个可迭代序列。注意,生成器的实现通常包含一个 `promise_type` 和一个迭代器包装器。 ## 5. 协程在异步 I/O 中的应用 现代 C++ 协程常与 `asio`、`libuv` 或自定义事件循环结合,实现高性能网络服务器。示例使用 Boost.Asio 的协程: “`cpp #include #include #include using namespace boost::asio; using namespace boost::asio::experimental::awaitable_operators; awaitable async_echo(tcp::socket socket) { char data[1024]; for (;;) { std::size_t n = co_await async_read(socket, buffer(data), use_awaitable); if (n == 0) break; co_await async_write(socket, buffer(data, n), use_awaitable); } } “` 上述代码在 ASIO 的事件循环中协程化读写操作,省去了手写状态机。 ## 6. 性能与注意事项 – **协程挂起的成本**:每次挂起/恢复需要保存/恢复寄存器和栈帧,成本相对轻量,但不宜频繁挂起。 – **协程句柄管理**:必须手动销毁 `std::coroutine_handle`,否则会导致内存泄漏。推荐使用 RAII 包装。 – **异常传播**:协程中的异常会被 `promise_type::unhandled_exception` 处理,默认行为是调用 `std::terminate`。可自定义 `handle_exception`。 – **线程安全**:协程本身不保证线程安全。若跨线程挂起,需确保挂起点和恢复点在同步环境中。 ## 7. 结语 C++20 的协程特性极大地简化了异步编程、生成器和协作式多任务的实现。掌握 `co_await`、`co_yield` 与 `co_return` 的使用,以及自定义 `promise_type`,即可在任何需要异步控制流的场景中使用协程。随着 C++23 的到来,标准化的 `std::generator `、`std::async` 的协程化将进一步降低使用门槛,为 C++ 开发者打开了新的编程范式。祝你在协程世界中玩得愉快,写出高效、优雅的代码!

发表评论