在 C++23 标准中,协程(Coroutines)作为一种语言层面的异步编程原语,已经成熟到可以直接用于生产代码。本文从协程的基本概念、实现原理,到如何在实际项目中用它来实现异步 I/O、生成器、并发管道等场景,给出完整的示例和实用技巧。
1. 协程的核心概念
| 术语 | 含义 | 关键字 |
|---|---|---|
| 协程函数 | 产生 awaitable 对象的函数 |
co_await, co_yield, co_return |
| awaiter | 抽象异步操作 | 需要实现 await_ready(), await_suspend(), await_resume() |
| promise_type | 协程对象的状态容器 | 通过 std::experimental::coroutine_handle 与 promise_type 交互 |
重要点:协程本身是同步执行的,直到遇到
co_await,此时才会挂起。挂起后,控制权返回给调用者,等到异步事件完成后再恢复。
2. 一个完整的异步 I/O 示例
下面演示如何使用 C++23 的协程与标准库的 std::experimental::net(假设已实现)实现一个简单的 HTTP GET 客户端。
#include <iostream>
#include <string>
#include <experimental/coroutine>
#include <experimental/net>
using namespace std::experimental;
namespace net = std::experimental::net;
// awaitable 代表一个异步操作
struct AsyncRead {
net::tcp_stream stream;
std::vector <char> buffer;
std::experimental::coroutine_handle<> handle; // 协程句柄
// 当异步读完成后恢复协程
void operator()() {
handle.resume();
}
// await_ready:是否同步完成
bool await_ready() noexcept { return false; }
// await_suspend:挂起协程,并启动异步读
bool await_suspend(std::experimental::coroutine_handle<> h) noexcept {
handle = h;
stream.async_read_some(buffer.data(), buffer.size(), *this);
return true; // 挂起
}
// await_resume:获取结果
std::size_t await_resume() noexcept { return buffer.size(); }
};
async<std::vector<char>> fetch(const std::string& host, const std::string& path) {
net::tcp_stream stream;
stream.connect(host, "80"); // 同步 connect
// 发送 HTTP 请求
std::string req = "GET " + path + " HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Connection: close\r\n\r\n";
stream.write(req.data(), req.size());
// 等待响应
AsyncRead reader{stream, std::vector <char>(8192)};
co_await reader;
// 读取完毕后返回缓冲区
co_return std::move(reader.buffer);
}
int main() {
auto task = fetch("example.com", "/");
task.wait(); // 阻塞直到协程完成
std::cout << "收到 " << task.result().size() << " 字节\n";
}
要点
- `async ` 是 C++23 标准中的 `std::experimental::coroutine_traits` 所生成的任务类型。
await_ready、await_suspend、await_resume三个函数构成 awaitable 的完整协议。handle.resume()是恢复协程的关键调用。
3. 协程生成器(Generator)
协程可以用来实现惰性序列(Generator),大大简化迭代器实现。
#include <experimental/coroutine>
#include <iostream>
template<typename T>
struct Generator {
struct promise_type {
T current_value;
std::experimental::suspend_always yield_value(T value) {
current_value = value;
return {};
}
std::experimental::suspend_always initial_suspend() { return {}; }
std::experimental::suspend_always final_suspend() noexcept { return {}; }
Generator get_return_object() {
return Generator{std::experimental::coroutine_handle <promise_type>::from_promise(*this)};
}
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
using handle_t = std::experimental::coroutine_handle <promise_type>;
handle_t coro;
explicit Generator(handle_t h) : coro(h) {}
~Generator() { if (coro) coro.destroy(); }
T next() {
coro.resume();
return coro.promise().current_value;
}
bool done() const { return !coro || coro.done(); }
};
Generator <int> fib() {
int a = 0, b = 1;
co_yield a;
while (true) {
co_yield b;
int tmp = a + b;
a = b;
b = tmp;
}
}
int main() {
auto g = fib();
for (int i = 0; i < 10; ++i)
std::cout << g.next() << ' ';
}
优点
- 惰性:每次
next()调用才计算下一个值。- 状态封装:协程内部维护了迭代状态,无需手写迭代器类。
4. 并发管道(Pipeline)
协程可以串联多层处理逻辑,形成流水线(Pipeline),非常适合大规模并行数据处理。
#include <experimental/coroutine>
#include <queue>
#include <thread>
#include <atomic>
template<typename In, typename Out>
class Pipeline {
std::queue <In> input;
std::queue <Out> output;
std::atomic <bool> done{false};
struct awaitable {
Pipeline& pipe;
awaitable(Pipeline& p) : pipe(p) {}
bool await_ready() noexcept { return !pipe.input.empty(); }
bool await_suspend(std::experimental::coroutine_handle<> h) {
std::thread([&]{
// Simulate work
std::this_thread::sleep_for(std::chrono::milliseconds(100));
pipe.input.pop();
h.resume();
}).detach();
return true;
}
In await_resume() { return pipe.input.front(); }
};
public:
void push(In v) { input.push(v); }
std::experimental::generator <Out> run() {
while (!done || !input.empty()) {
auto v = co_await awaitable{*this};
// 处理
output.push(v * 2);
co_yield output.front();
output.pop();
}
}
void stop() { done = true; }
};
实际场景:日志收集、视频帧处理、网络流式传输等。
5. 常见陷阱与调试技巧
| 误区 | 解决方案 |
|---|---|
| 协程挂起后忘记恢复 | 通过 co_await 的 awaitable 实现 await_suspend 时务必调用 handle.resume() |
| 资源泄漏 | promise_type 的 final_suspend 里记得销毁协程句柄;使用 std::experimental::coroutine_handle::destroy() |
| 性能不佳 | 大量协程频繁创建/销毁会导致堆碎片;可使用协程池或预分配内存 |
| 调试困难 | 由于协程内部状态隐藏,建议使用 -fsanitize=address + -fno-inline,或手动打印状态日志 |
6. 小结
- C++23 的协程提供了完整的异步编程模型,兼容传统同步代码,易于集成。
- 通过实现自定义
awaiter和promise_type,可与任何异步 I/O 或任务调度系统结合。 - 生成器、管道等模式可以让代码更简洁、可维护。
实践建议:先从简单的异步 I/O 开始,逐步扩展到协程生成器和并发管道;并关注协程池、内存管理等高级细节,才能真正发挥协程在高性能 C++ 应用中的优势。