协程(coroutine)是 C++20 标准中引入的一项强大功能,它让我们可以在单线程环境下实现类似多线程的并发执行。与传统的线程相比,协程的上下文切换开销极低,能够在不产生额外线程的情况下实现异步编程。下面我们从协程的基本概念、实现原理、常见使用场景以及实际编码示例几个方面来探讨协程在 C++ 开发中的应用。
一、协程的基本概念
- 协作式多任务:协程是一种“合作式”的并发模型。执行流程需要协程自身主动挂起(
co_await、co_yield或co_return),而不是被操作系统强制抢占。 - 协程句柄(coroutine handle):
std::coroutine_handle对象封装了协程的执行上下文。通过它可以手动启动、暂停或销毁协程。 - 悬挂点(suspension point):在协程函数体内,遇到
co_await、co_yield或co_return时会产生悬挂点,协程的状态会被保存,并返回给调用者。 - 尾随返回类型(co_return):协程函数的返回类型需要实现
promise_type,它定义了协程完成时的行为,例如如何返回值、如何处理异常等。
二、协程的实现原理
- 状态机化:编译器把协程函数编译成一个状态机,状态机的每个分支对应一个悬挂点。
- 栈上内存:协程的局部变量(在
co_yield/co_await前后需要保留的)被拆解成“悬挂点状态”,存放在堆或栈上。 - promise 对象:每个协程都有一个
promise_type对象,负责管理协程的生命周期、返回值、异常等。 - 协程句柄:通过 `std::coroutine_handle ` 与协程进行交互,启动或继续协程执行。
三、典型使用场景
- 异步 I/O:结合
co_await与异步 I/O 库(如asio)实现无阻塞网络通信。 - 协程生成器:利用
co_yield创建惰性序列,例如无限斐波那契数列。 - 任务调度:将协程与事件循环结合,构建轻量级调度器,实现高性能并发服务器。
- 状态机实现:将传统的 if-else 或 switch 逻辑转换为协程式状态机,代码更易维护。
四、代码示例
下面给出一个简单的协程生成器示例,演示如何使用 co_yield 产生无限斐波那契数列,并在主函数中迭代读取前 10 个值。
#include <iostream>
#include <coroutine>
#include <optional>
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 unhandled_exception() { std::exit(1); }
void return_void() {}
};
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro;
explicit generator(handle_type h) : coro(h) {}
generator(const generator&) = delete;
generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; }
~generator() { if (coro) coro.destroy(); }
bool next() { return coro.resume(); }
T value() const { return coro.promise().current_value; }
};
generator<unsigned long long> fib_sequence(unsigned long long limit) {
unsigned long long a = 0, b = 1;
while (a <= limit) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}
int main() {
auto seq = fib_sequence(100);
int count = 0;
while (seq.next() && count < 10) {
std::cout << seq.value() << ' ';
++count;
}
std::cout << '\n';
return 0;
}
运行结果
0 1 1 2 3 5 8 13 21 34
五、常见坑与注意事项
- 悬挂点的生命周期:确保协程句柄在协程完成前未被销毁,否则会导致未定义行为。
- 异常安全:如果协程中抛出异常,
promise_type::unhandled_exception会被调用。可以在这里做清理或记录日志。 - 协程句柄的拷贝:默认不可拷贝,建议使用移动语义。
- 协程与线程交互:在多线程环境中使用协程时,需要注意线程安全,例如对
promise_type的访问进行同步。
六、总结
C++20 的协程为实现高性能、低开销的异步编程提供了天然的工具。掌握协程的基本概念、编译实现以及常见使用场景,能够让我们在构建网络服务器、游戏引擎、数据处理管道等领域时写出更简洁、更易维护的代码。随着 C++ 标准库与生态的不断完善,协程将成为现代 C++ 开发不可或缺的一部分。