协程(Coroutine)是 C++20 标准中为解决异步编程带来的复杂性而加入的一项功能。它们通过把“暂停”和“恢复”的概念显式化,使得编写顺序化代码的同时也能处理异步或惰性计算。下面将从协程的基本概念、关键字使用、状态机实现、以及最佳实践等方面进行系统阐述,并给出实战示例,帮助你快速掌握并在项目中合理使用协程。
1. 协程的基本概念
- 挂起点:协程可以在任何地方挂起(
co_await、co_yield、co_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++ 开发中扮演越来越重要的角色。祝你在协程世界里玩得愉快!