C++20 标准正式引入协程(coroutine)概念,为编写异步、非阻塞代码提供了更简洁、高效的语法与机制。与传统的线程或基于回调的异步模型相比,协程能够让代码保持同步式的书写风格,却在底层实现上通过轻量级的状态机实现挂起与恢复,从而显著降低上下文切换成本,提升系统吞吐量。下面,我们将从协程的基本语法、实现原理以及实际应用场景展开讨论,并给出完整示例。
1. 协程基本概念
协程是一种比线程更轻量的执行单元,它能够在任意位置挂起(co_await、co_yield、co_return)并在需要时恢复执行。协程本身不需要像线程那样拥有完整的栈,只有在挂起点附近维护一个“状态机”状态,真正需要保留的局部变量会被“升到堆上”或保存在协程框架管理的缓存中。
C++20 对协程的支持主要体现在以下几个关键词上:
co_await:挂起当前协程,等待一个 Awaitable 对象完成后继续执行。
co_yield:将一个值返回给调用方,暂停协程。
co_return:结束协程并返回最终结果。
std::suspend_always / std::suspend_never:控制协程的挂起策略。
2. Awaitable 与协程句柄
一个可 await 的对象必须满足 Awaitable 概念,最重要的成员函数是:
bool await_ready(); // 是否立即完成
void await_suspend(std::coroutine_handle<> h); // 挂起时的动作
auto await_resume(); // 完成后返回的结果
当调用 co_await obj; 时,编译器会把 obj 的三函数分别调用,从而决定协程是否挂起。协程句柄 `std::coroutine_handle
` 可以用来手动恢复协程、查询状态或获取返回值。
### 3. 一个简单的协程例子
下面演示一个 `async_sum` 函数,它接受两个整数,模拟异步延迟后返回它们之和。协程内部使用 `std::suspend_always` 进行挂起,然后在外部使用 `std::this_thread::sleep_for` 模拟耗时操作,最后通过 `co_return` 返回结果。
“`cpp
#include
#include
#include
#include
struct AsyncSum {
struct promise_type {
int result_;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
AsyncSum get_return_object() {
return AsyncSum{ std::coroutine_handle
::from_promise(*this) };
}
void return_value(int v) { result_ = v; }
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle
coro_;
explicit AsyncSum(std::coroutine_handle
h) : coro_(h) {}
~AsyncSum() { if (coro_) coro_.destroy(); }
int get() {
if (!coro_.done()) coro_.resume();
return coro_.promise().result_;
}
};
AsyncSum async_sum(int a, int b) {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
co_return a + b;
}
int main() {
auto task = async_sum(3, 4);
std::cout << "Result: " << task.get() << std::endl;
return 0;
}
“`
**说明**
– `async_sum` 本身就是一个协程。
– `coro_.resume()` 会执行到下一个挂起点;若协程已完成,`done()` 为 true。
– 这里我们用 `std::this_thread::sleep_for` 模拟 I/O 延迟;在真实项目中可替换为 `co_await` 一个异步 I/O 句柄,例如 Boost.Asio 的异步读取。
### 4. 组合协程实现异步管道
协程可以通过 `co_yield` 生成流式数据。例如实现一个异步文件行读取器:
“`cpp
#include
#include
#include
struct AsyncLineReader {
struct promise_type {
std::string line_;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
AsyncLineReader get_return_object() {
return AsyncLineReader{ std::coroutine_handle
::from_promise(*this) };
}
void yield_value(std::string&& v) { line_ = std::move(v); }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle
coro_;
explicit AsyncLineReader(std::coroutine_handle
h) : coro_(h) {}
~AsyncLineReader() { if (coro_) coro_.destroy(); }
bool next() {
if (coro_.done()) return false;
coro_.resume();
return !coro_.done();
}
std::string current() const { return coro_.promise().line_; }
};
AsyncLineReader read_file(const std::string& path) {
std::ifstream fin(path);
std::string line;
while (std::getline(fin, line)) {
co_yield std::move(line);
}
}
“`
使用示例:
“`cpp
auto reader = read_file(“data.txt”);
while (reader.next()) {
std::cout << reader.current() << '\n';
}
“`
### 5. 与线程池结合
为了真正实现高性能的异步 I/O,协程常配合线程池、事件循环或异步 I/O 库使用。C++20 标准库并没有提供完整的事件循环实现,但可使用第三方库(如 Boost.Asio、cppcoro、libuv 等)提供 `awaitable` 对象,协程就能与 OS 级 I/O 事件无缝绑定。
**示例:使用 Boost.Asio 的 async_read_until**
“`cpp
#include
#include
#include
#include
#include
using namespace boost::asio;
using awaitable_void = awaitable;
awaitable_void read_line(tcp::socket& sock) {
streambuf buf;
std::size_t n = co_await async_read_until(sock, buf, ‘\n’);
std::istream is(&buf);
std::string line;
std::getline(is, line);
std::cout << "Received: " << line << std::endl;
}
“`
在事件循环线程中运行 `co_spawn(io_context, read_line(sock), detached);` 即可。
### 6. 小结
– C++20 的协程为异步编程提供了语法糖,代码更易读。
– 协程通过 `std::suspend_always/never`、`await_ready` 等控制挂起点,真正实现了“无阻塞同步”风格。
– 与线程池、事件循环结合,可实现高性能 I/O 服务;在单线程或轻量级多线程环境下,协程还能显著降低资源消耗。
掌握协程后,你可以在网络服务器、游戏循环、图形渲染等高并发场景中,使用 C++ 的强类型与安全性,写出既简洁又高效的异步代码。祝你玩得愉快,写出更多精彩的协程示例!