C++20 中的协程:实现异步编程的全新方式

C++20 标准正式引入协程(coroutine)概念,为编写异步、非阻塞代码提供了更简洁、高效的语法与机制。与传统的线程或基于回调的异步模型相比,协程能够让代码保持同步式的书写风格,却在底层实现上通过轻量级的状态机实现挂起与恢复,从而显著降低上下文切换成本,提升系统吞吐量。下面,我们将从协程的基本语法、实现原理以及实际应用场景展开讨论,并给出完整示例。

1. 协程基本概念

协程是一种比线程更轻量的执行单元,它能够在任意位置挂起(co_awaitco_yieldco_return)并在需要时恢复执行。协程本身不需要像线程那样拥有完整的栈,只有在挂起点附近维护一个“状态机”状态,真正需要保留的局部变量会被“升到堆上”或保存在协程框架管理的缓存中。

C++20 对协程的支持主要体现在以下几个关键词上:

  • co_await:挂起当前协程,等待一个 Awaitable 对象完成后继续执行。
  • co_yield:将一个值返回给调用方,暂停协程。
  • co_return:结束协程并返回最终结果。
  • std::suspend_always / std::suspend_never:控制协程的挂起策略。

2. Awaitable 与协程句柄

一个可 await 的对象必须满足 Awaitable 概念,最重要的成员函数是:

bool await_ready();      // 是否立即完成
void await_suspend(std::coroutine_handle<> h); // 挂起时的动作
auto await_resume();     // 完成后返回的结果

当调用 co_await obj; 时,编译器会把 obj 的三函数分别调用,从而决定协程是否挂起。协程句柄 `std::coroutine_handle

` 可以用来手动恢复协程、查询状态或获取返回值。 ### 3. 一个简单的协程例子 下面演示一个 `async_sum` 函数,它接受两个整数,模拟异步延迟后返回它们之和。协程内部使用 `std::suspend_always` 进行挂起,然后在外部使用 `std::this_thread::sleep_for` 模拟耗时操作,最后通过 `co_return` 返回结果。 “`cpp #include #include #include #include struct AsyncSum { struct promise_type { int result_; std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } AsyncSum get_return_object() { return AsyncSum{ std::coroutine_handle ::from_promise(*this) }; } void return_value(int v) { result_ = v; } void unhandled_exception() { std::terminate(); } }; std::coroutine_handle coro_; explicit AsyncSum(std::coroutine_handle h) : coro_(h) {} ~AsyncSum() { if (coro_) coro_.destroy(); } int get() { if (!coro_.done()) coro_.resume(); return coro_.promise().result_; } }; AsyncSum async_sum(int a, int b) { // 模拟耗时操作 std::this_thread::sleep_for(std::chrono::milliseconds(100)); co_return a + b; } int main() { auto task = async_sum(3, 4); std::cout << "Result: " << task.get() << std::endl; return 0; } “` **说明** – `async_sum` 本身就是一个协程。 – `coro_.resume()` 会执行到下一个挂起点;若协程已完成,`done()` 为 true。 – 这里我们用 `std::this_thread::sleep_for` 模拟 I/O 延迟;在真实项目中可替换为 `co_await` 一个异步 I/O 句柄,例如 Boost.Asio 的异步读取。 ### 4. 组合协程实现异步管道 协程可以通过 `co_yield` 生成流式数据。例如实现一个异步文件行读取器: “`cpp #include #include #include struct AsyncLineReader { struct promise_type { std::string line_; std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } AsyncLineReader get_return_object() { return AsyncLineReader{ std::coroutine_handle ::from_promise(*this) }; } void yield_value(std::string&& v) { line_ = std::move(v); } void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handle coro_; explicit AsyncLineReader(std::coroutine_handle h) : coro_(h) {} ~AsyncLineReader() { if (coro_) coro_.destroy(); } bool next() { if (coro_.done()) return false; coro_.resume(); return !coro_.done(); } std::string current() const { return coro_.promise().line_; } }; AsyncLineReader read_file(const std::string& path) { std::ifstream fin(path); std::string line; while (std::getline(fin, line)) { co_yield std::move(line); } } “` 使用示例: “`cpp auto reader = read_file(“data.txt”); while (reader.next()) { std::cout << reader.current() << '\n'; } “` ### 5. 与线程池结合 为了真正实现高性能的异步 I/O,协程常配合线程池、事件循环或异步 I/O 库使用。C++20 标准库并没有提供完整的事件循环实现,但可使用第三方库(如 Boost.Asio、cppcoro、libuv 等)提供 `awaitable` 对象,协程就能与 OS 级 I/O 事件无缝绑定。 **示例:使用 Boost.Asio 的 async_read_until** “`cpp #include #include #include #include #include using namespace boost::asio; using awaitable_void = awaitable; awaitable_void read_line(tcp::socket& sock) { streambuf buf; std::size_t n = co_await async_read_until(sock, buf, ‘\n’); std::istream is(&buf); std::string line; std::getline(is, line); std::cout << "Received: " << line << std::endl; } “` 在事件循环线程中运行 `co_spawn(io_context, read_line(sock), detached);` 即可。 ### 6. 小结 – C++20 的协程为异步编程提供了语法糖,代码更易读。 – 协程通过 `std::suspend_always/never`、`await_ready` 等控制挂起点,真正实现了“无阻塞同步”风格。 – 与线程池、事件循环结合,可实现高性能 I/O 服务;在单线程或轻量级多线程环境下,协程还能显著降低资源消耗。 掌握协程后,你可以在网络服务器、游戏循环、图形渲染等高并发场景中,使用 C++ 的强类型与安全性,写出既简洁又高效的异步代码。祝你玩得愉快,写出更多精彩的协程示例!

发表评论