C++20 里协程的使用与最佳实践

协程(Coroutine)是 C++20 标准中为解决异步编程带来的复杂性而加入的一项功能。它们通过把“暂停”和“恢复”的概念显式化,使得编写顺序化代码的同时也能处理异步或惰性计算。下面将从协程的基本概念、关键字使用、状态机实现、以及最佳实践等方面进行系统阐述,并给出实战示例,帮助你快速掌握并在项目中合理使用协程。

1. 协程的基本概念

  • 挂起点:协程可以在任何地方挂起(co_awaitco_yieldco_return),随后被恢复执行。
  • 返回类型:协程的返回类型由 std::coroutine_traits 生成,典型的返回类型是 `std::future `、`std::generator` 或者自定义的 `task`。
  • 状态机:编译器会把协程体转换成一个状态机,挂起点对应不同的状态。每次挂起/恢复会进入对应状态继续执行。

2. 关键字与语法

关键字 用途
co_await 等待一个可等待对象,挂起协程直至完成
co_yield 在生成器中产生一个值并挂起,随后可继续生成
co_return 结束协程并返回值
co_spawn (可选)将协程调度到异步执行上下文

协程主体的结构大致如下:

std::future <int> asyncAdd(int a, int b) {
    co_return a + b;           // 直接返回值
}

或更常见的异步等待:

std::future <int> asyncAdd(int a, int b) {
    int result = co_await asyncCompute(a);   // 等待一个子协程
    result += co_await asyncCompute(b);
    co_return result;
}

3. 自定义协程包装器(`task

`) 大多数标准库不直接提供 `task` 类型,常用的做法是自定义一个简单包装器: “`cpp template struct task { struct promise_type { T value_; std::exception_ptr exc_; std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { exc_ = std::current_exception(); } template void return_value(U&& val) { value_ = std::forward (val); } task get_return_object() { return task{std::coroutine_handle ::from_promise(*this)}; } }; std::coroutine_handle coro_; explicit task(std::coroutine_handle h) : coro_(h) {} ~task() { if (coro_) coro_.destroy(); } T get() { coro_.resume(); if (coro_.promise().exc_) std::rethrow_exception(coro_.promise().exc_); return coro_.promise().value_; } }; “` 使用示例: “`cpp task fib(int n) { if (n #include namespace asio = boost::asio; using asio::awaitable; using asio::use_awaitable; using asio::streambuf; using asio::async_read_until; awaitable read_file(const std::string& path) { asio::io_context io; std::ifstream file(path, std::ios::binary); if (!file) co_return {}; streambuf sb; co_await async_read_until(file, sb, ‘\0’, use_awaitable); std::istream is(&sb); std::string content((std::istreambuf_iterator (is)), std::istreambuf_iterator ()); co_return content; } “` 此处 `awaitable` 是 `asio` 对协程的包装,`use_awaitable` 指示 `async_read_until` 返回一个 awaitable 对象,协程挂起直到文件读取完成。 ## 7. 最佳实践 1. **只在需要时使用协程**:协程最适合 I/O 密集型、需要并发处理的场景。CPU 密集型任务建议使用线程池或 SIMD。 2. **保持协程体轻量**:避免在协程内部做大量计算,尽量把工作分解为子协程或普通同步函数。 3. **统一错误处理**:通过 promise 的 `unhandled_exception` 统一捕获异常,避免漏掉异常导致协程意外终止。 4. **配合调度器**:使用线程池或事件循环调度协程,避免挂起点在主线程阻塞。 5. **测试与性能监控**:协程的隐藏状态机可能导致调试困难,建议在开发阶段使用 `-fno-inline` 或 `-fno-inline-functions` 进行调试,监控 CPU 和内存使用情况。 ## 8. 小结 C++20 协程为 C++ 提供了一套优雅的异步编程模型。通过掌握基本语法、状态机原理以及与异步库的结合,你可以在项目中更高效地处理 I/O、网络、文件等异步任务。保持协程体轻量、统一异常处理、合理使用调度器,是编写健壮协程代码的关键。随着标准化和工具链的完善,协程将在未来的 C++ 开发中扮演越来越重要的角色。祝你在协程世界里玩得愉快!

发表评论