C++20 引入了协程(coroutine)概念,使得在单线程中实现异步操作变得更加自然和高效。相比传统的回调或线程池,协程能够在需要等待 I/O、网络请求等耗时操作时挂起执行,随后恢复,整个流程几乎不需要显式的状态机管理。本文将从协程的基本概念、实现细节以及实际应用场景展开,帮助你快速掌握 C++20 协程的使用方法。
1. 协程基本语法
协程函数通过关键字 co_await、co_yield 或 co_return 来定义。与普通函数不同,协程函数的返回类型不是 T,而是一个 promise type,通常是 `std::generator
`、`std::task` 或自定义类型。
“`cpp
#include
#include
struct hello {
struct promise_type {
auto get_return_object() { return hello{}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};
hello world() {
std::cout
#include
template
struct task {
struct promise_type {
std::promise
promise;
task get_return_object() { return task{promise.get_future()}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { promise.set_exception(std::current_exception()); }
void return_value(T value) { promise.set_value(value); }
};
std::future
fut;
explicit task(std::future
f) : fut(std::move(f)) {}
operator std::future
&() { return fut; }
};
task
async_add(int a, int b) {
co_return a + b;
}
“`
使用方式:
“`cpp
auto t = async_add(3, 4);
int result = t.fut.get(); // 结果为 7
“`
### 2.2 生成器(Generator)
C++20 标准提供 `std::generator`(在实验版 `
` 头文件中)。以下示例生成斐波那契数列:
“`cpp
#include
#include
std::generator
fib(int n) {
int a = 0, b = 1;
for (int i = 0; i ); // 挂起并注册恢复
T await_resume(); // 获得结果
};
“`
例如,使用 `std::future`:
“`cpp
struct future_awaiter {
std::future
fut;
bool await_ready() const noexcept { return fut.wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
void await_suspend(std::coroutine_handle h) {
std::thread([f = std::move(fut), h]() mutable {
f.wait();
h.resume();
}).detach();
}
int await_resume() { return fut.get(); }
};
future_awaiter make_awaiter(std::future
f) { return {std::move(f)}; }
“`
## 4. 常见使用场景
1. **网络 I/O**:配合异步 I/O 库(如 Boost.Asio 的 `async_read`)实现非阻塞服务器。
2. **文件读写**:与异步文件系统接口结合,在文件读取完成前挂起。
3. **协作式多任务**:在单线程游戏循环中通过协程实现脚本式 AI 或动画控制。
4. **生成器**:遍历大数据集时按需产生元素,节省内存。
## 5. 性能注意
– 协程切换是轻量级的,但不如线程切换原生;仍需注意过度挂起导致的上下文切换。
– `std::suspend_never` 可以减少不必要的挂起,提升性能。
– 对于频繁小任务,建议将任务合并为单一协程以降低协程管理成本。
## 6. 代码示例:异步下载文件
“`cpp
#include
#include
#include
struct async_download {
asio::ip::tcp::socket sock;
std::string data;
async_download(asio::io_context& io) : sock(io) {}
struct promise_type {
async_download* self;
auto get_return_object() { return self; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
auto operator co_await() const {
struct awaiter {
async_download& self;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle h) {
asio::async_read(self.sock, asio::buffer(self.data),
[h](std::error_code ec, std::size_t) mutable {
h.resume();
});
}
std::string await_resume() { return self.data; }
};
return awaiter{*const_cast(this)};
}
};
async_download fetch(asio::io_context& io, std::string host, std::string port) {
using namespace asio;
tcp::resolver resolver(io);
auto endpoints = resolver.resolve(host, port);
async_download d(io);
co_await asio::async_connect(d.sock, endpoints, use_awaitable);
co_return d;
}
int main() {
asio::io_context io;
auto task = fetch(io, “example.com”, “80”);
io.run();
std::cout