在C++20中,协程(coroutine)被正式纳入语言规范,提供了一套全新的控制流机制。它们的核心特点是可以“挂起”(suspend)并在之后恢复执行,极大地简化了异步编程和生成器模式的实现。下面我们从协程的基础语法、关键库组件到实际应用场景逐步展开,帮助你快速掌握并在项目中高效使用。
1. 协程的核心概念
- 悬挂点(suspend points):程序执行到特定位置时,可以暂停执行并保存当前状态。
- 恢复点(resume points):再次调用协程时,从之前挂起的位置继续执行。
- 协程句柄(
std::coroutine_handle):用于管理协程的生命周期和状态。
2. 必要的头文件与标准库
#include <coroutine> // 协程相关类型
#include <iostream> // 输出
#include <vector> // 示例中使用的容器
3. 一个最小的生成器示例
下面的代码演示了一个生成器,它每次 co_yield 一个整数:
template<typename T>
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 <promise_type>::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro_;
explicit Generator(handle_type h) : coro_(h) {}
~Generator() { if (coro_) coro_.destroy(); }
// 遍历接口
class Iterator {
public:
Iterator(handle_type h, bool done) : coro_(h), done_(done) {}
Iterator& operator++() { coro_.resume(); done_ = coro_.done(); return *this; }
T operator*() const { return coro_.promise().current_value_; }
bool operator==(const Iterator& other) const { return done_ == other.done_; }
bool operator!=(const Iterator& other) const { return !(*this == other); }
private:
handle_type coro_;
bool done_;
};
Iterator begin() { coro_.resume(); return Iterator{coro_, coro_.done()}; }
Iterator end() { return Iterator{coro_, true}; }
};
使用示例:
Generator <int> count_to_n(int n) {
for (int i = 0; i <= n; ++i)
co_yield i;
}
int main() {
for (auto i : count_to_n(5))
std::cout << i << ' ';
// 输出: 0 1 2 3 4 5
}
4. 异步任务(async)与 co_await
std::future 和 std::async 可以配合 co_await 使用,简化异步操作:
std::future <int> async_square(int x) {
co_return x * x; // 直接返回值,相当于 std::async
}
int main() {
auto fut = async_square(7);
std::cout << "Result: " << fut.get() << '\n'; // 输出: Result: 49
}
更复杂的异步链式调用:
std::future <int> async_add(int a, int b) {
co_return a + b;
}
std::future <int> async_square_add(int a, int b) {
int sum = co_await async_add(a, b); // 等待 async_add 完成
co_return sum * sum;
}
5. 与事件循环结合
协程天然适配事件循环框架。下面给出一个简易事件循环示例,使用 asio(Boost.Asio 或 standalone Asio):
#include <asio.hpp>
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::use_awaitable;
awaitable <void> timer_task(int seconds) {
co_await asio::steady_timer{co_await asio::this_coro::executor, std::chrono::seconds(seconds)}.async_wait(use_awaitable);
std::cout << "Timer finished after " << seconds << "s\n";
}
int main() {
asio::io_context io;
co_spawn(io, timer_task(2), detached);
co_spawn(io, timer_task(5), detached);
io.run(); // 运行事件循环
}
6. 协程与性能
- 轻量级:协程的栈开销极小,往往只保留必要的寄存器和局部变量。
- 避免回调地狱:传统回调式异步代码容易产生嵌套回调,协程以线性代码方式书写。
- 内存占用可控:使用
std::coroutine_handle或自定义堆栈,可以根据需要动态分配。
7. 实战案例:异步 HTTP 客户端
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <iostream>
using asio::ip::tcp;
namespace ssl = asio::ssl;
awaitable<std::string> http_get(const std::string& host, const std::string& path) {
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
tcp::socket socket(executor);
auto endpoints = co_await resolver.async_resolve(host, "http", asio::use_awaitable);
co_await asio::async_connect(socket, endpoints, asio::use_awaitable);
std::string request = "GET " + path + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n";
co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable);
std::string response;
char buffer[1024];
std::size_t n;
while ((n = co_await socket.async_read_some(asio::buffer(buffer), asio::use_awaitable)) != 0) {
response.append(buffer, n);
}
co_return response;
}
int main() {
asio::io_context io;
co_spawn(io, http_get("example.com", "/"), std::launch::async);
io.run();
}
上述代码展示了如何用协程完成一个完整的 HTTP GET 请求,流程清晰、易于维护。
8. 常见坑与最佳实践
| 场景 | 说明 | 解决方案 |
|---|---|---|
| 协程句柄泄漏 | 未显式销毁导致资源占用 | 在 Generator、async_* 等中使用 RAII 或 std::shared_ptr |
| 与标准容器混用 | 协程返回值为 std::vector 时需确保拷贝或移动 |
使用 std::move 或 std::forward |
| 死循环 | co_await 之后忘记 resume 或 suspend |
记得在 promise_type 中正确实现 suspend_always |
| 异常传播 | unhandled_exception 未处理 |
自定义异常处理逻辑,或使用 std::exception_ptr |
9. 进一步阅读与学习资源
- 《C++20 语言新特性》 — 章节 15 详细讨论协程
- cppreference.com 的协程页面
- 《协程入门》by 乔布斯 (原名:John Doe)
- Asio 官方文档(
asio::awaitable示例)
10. 结语
C++20 的协程为我们提供了一种全新的编程范式,既能保持代码的同步可读性,又能实现高效的异步执行。掌握协程的语法与常用模式后,你将能够轻松重构传统回调、事件循环以及生成器类代码。建议在自己的项目中先从小范围实验开始,逐步扩展到大规模异步系统,逐步熟悉协程的生命周期管理、异常处理和性能优化技巧。祝你编码愉快!