在 C++20 之前,协程的实现通常依赖于第三方库或手写生成器(如 Boost.Coroutine)。C++20 标准直接将协程语言特性纳入编译器,简化了异步编程和生成器的实现。本文将从协程的基本概念、关键语法、实现细节以及实际应用场景三方面,系统阐述 C++20 协程的使用方法。
1. 协程概念回顾
协程是一种比线程更轻量级的同步机制,允许函数在执行过程中挂起(suspend)并在之后恢复。协程的核心特性是:
- 挂起点(
co_await、co_yield、co_return)
- 协程句柄(
std::coroutine_handle)
- 协程体(
co_* 关键字嵌入的函数体)
- 协程的状态机(编译器自动生成)
协程的执行流程类似于生成器,但可更灵活地处理异步 IO、事件循环等复杂逻辑。
2. C++20 协程的语法
2.1 协程函数声明
协程函数必须返回一个具有 std::suspend_always 或 std::suspend_never 等协程特性的类型,最常见的是自定义的 promise_type。标准库中提供了 `std::generator
`(C++23),但 C++20 仅提供了 `std::coroutine_handle`。
“`cpp
#include
#include
struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() {
return MyCoroutine{std::coroutine_handle
::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle
handle;
MyCoroutine(std::coroutine_handle
h) : handle(h) {}
~MyCoroutine() { if (handle) handle.destroy(); }
};
MyCoroutine example() {
std::cout << "Before suspend\n";
co_await std::suspend_always{};
std::cout << "After suspend\n";
}
“`
### 2.2 `co_await`、`co_yield` 与 `co_return`
– `co_await`:等待一个可 `awaitable` 的对象完成。它可以是一个 `std::future`, `std::promise`, 或自定义的 `awaitable` 结构。
– `co_yield`:生成一个值,类似 `yield` 的行为。协程返回一个迭代器,外部通过 `co_yield` 获取值。
– `co_return`:结束协程并返回值(若函数返回值非 `void`)。
## 3. 实现自定义 `awaitable`
下面演示一个简易的异步延时协程。
“`cpp
#include
#include
#include
struct Sleep {
std::chrono::milliseconds duration;
struct awaiter {
Sleep& self;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle h) noexcept {
std::thread([self = self.duration, h]() {
std::this_thread::sleep_for(self);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
auto operator co_await() noexcept { return awaiter{*this}; }
};
std::coroutine_handle demo() {
std::cout << "Start\n";
co_await Sleep{std::chrono::milliseconds(500)};
std::cout << "Half a second later\n";
co_await Sleep{std::chrono::milliseconds(500)};
std::cout << "Another half a second later\n";
}
“`
上述代码展示了如何让协程在后台线程中等待一段时间后恢复执行。需要注意的是,协程的挂起点可以跨线程完成。
## 4. 生成器示例:斐波那契序列
“`cpp
#include
#include
template
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
::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle
handle;
Generator(std::coroutine_handle
h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
struct Iterator {
std::coroutine_handle
handle;
bool operator!=(std::default_sentinel_t) { return !handle.done(); }
Iterator& operator++() { handle.resume(); return *this; }
T operator*() const { return handle.promise().current_value; }
};
Iterator begin() { handle.resume(); return Iterator{handle}; }
std::default_sentinel_t end() { return {}; }
};
Generator
fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int tmp = a;
a = b;
b = tmp + b;
}
}
int main() {
for (auto val : fibonacci(10))
std::cout << val << ' ';
}
“`
该生成器示例演示了如何利用 `co_yield` 返回一个可迭代序列。注意,生成器的实现通常包含一个 `promise_type` 和一个迭代器包装器。
## 5. 协程在异步 I/O 中的应用
现代 C++ 协程常与 `asio`、`libuv` 或自定义事件循环结合,实现高性能网络服务器。示例使用 Boost.Asio 的协程:
“`cpp
#include
#include
#include
using namespace boost::asio;
using namespace boost::asio::experimental::awaitable_operators;
awaitable
async_echo(tcp::socket socket) {
char data[1024];
for (;;) {
std::size_t n = co_await async_read(socket,
buffer(data),
use_awaitable);
if (n == 0) break;
co_await async_write(socket, buffer(data, n), use_awaitable);
}
}
“`
上述代码在 ASIO 的事件循环中协程化读写操作,省去了手写状态机。
## 6. 性能与注意事项
– **协程挂起的成本**:每次挂起/恢复需要保存/恢复寄存器和栈帧,成本相对轻量,但不宜频繁挂起。
– **协程句柄管理**:必须手动销毁 `std::coroutine_handle`,否则会导致内存泄漏。推荐使用 RAII 包装。
– **异常传播**:协程中的异常会被 `promise_type::unhandled_exception` 处理,默认行为是调用 `std::terminate`。可自定义 `handle_exception`。
– **线程安全**:协程本身不保证线程安全。若跨线程挂起,需确保挂起点和恢复点在同步环境中。
## 7. 结语
C++20 的协程特性极大地简化了异步编程、生成器和协作式多任务的实现。掌握 `co_await`、`co_yield` 与 `co_return` 的使用,以及自定义 `promise_type`,即可在任何需要异步控制流的场景中使用协程。随着 C++23 的到来,标准化的 `std::generator
`、`std::async` 的协程化将进一步降低使用门槛,为 C++ 开发者打开了新的编程范式。祝你在协程世界中玩得愉快,写出高效、优雅的代码!