C++20 为协程提供了官方支持,使得异步编程和生成器等模式得以在语言层面优雅实现。本文将先从协程的底层原理说起,然后给出一个完整的协程生成器示例,并讨论常见的使用场景与注意事项。
1. 协程的基本概念
协程是可挂起和恢复的函数。与传统线程不同,协程的切换由程序控制,成本更低,适合大量并发的场景。C++20 通过三大关键类型实现协程:
std::coroutine_handle– 负责协程的生命周期管理。std::suspend_always/std::suspend_never– 决定协程何时挂起或不挂起。promise_type– 用户定义的协程承诺,用于传递返回值、异常以及挂起/恢复逻辑。
协程的执行流程:
- 调用协程函数时,编译器生成一个状态机。
- 初始挂起(如果
initial_suspend()返回suspend_always)。 - 通过
co_await、co_yield或co_return控制挂起/恢复。 - 当协程结束时,编译器会自动清理资源并返回
promise_type。
2. 一个简单的生成器协程
下面实现一个整数序列生成器,演示如何使用 co_yield 和 promise_type。
#include <coroutine>
#include <iostream>
#include <optional>
template<typename T>
struct generator {
struct promise_type {
std::optional <T> current_value;
// 当协程第一次被调用时执行
generator get_return_object() {
return generator{
std::coroutine_handle <promise_type>::from_promise(*this)
};
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::exit(1); }
// 通过 co_yield 设置返回值
std::suspend_always yield_value(T value) noexcept {
current_value = std::move(value);
return {};
}
void return_void() {}
};
std::coroutine_handle <promise_type> coro;
generator(std::coroutine_handle <promise_type> h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
bool move_next() {
coro.resume();
return !coro.done();
}
T current() const { return *coro.promise().current_value; }
};
generator <int> range(int start, int end) {
for (int i = start; i <= end; ++i)
co_yield i;
}
使用方式:
int main() {
auto seq = range(1, 5);
while (seq.move_next())
std::cout << seq.current() << ' ';
// 输出:1 2 3 4 5
}
该示例展示了:
co_yield用来产生值。promise_type保存当前值。move_next调用resume,让协程继续执行到下一个挂起点。
3. 异步协程示例
C++20 的协程还可以与 std::future、std::async 结合,形成 co_await 的异步调用。
#include <future>
#include <iostream>
#include <thread>
std::future <int> async_add(int a, int b) {
return std::async(std::launch::async, [a, b]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return a + b;
});
}
std::future <int> compute() {
int x = co_await async_add(5, 10); // 异步等待
int y = co_await async_add(x, 20);
co_return y;
}
此处 co_await 会在 async_add 返回的 std::future 完成时恢复协程,从而实现简洁的异步链。
4. 实战场景与注意事项
| 场景 | 协程优势 | 注意点 |
|---|---|---|
| 生成器 | 轻量级、无额外线程 | 必须手动管理生命周期,避免悬空句柄 |
| 异步 IO | 代码可读性高 | 需要与事件循环或线程池配合 |
| 协作式调度 | 能精准控制任务切换 | 过度使用会导致堆栈碎片化 |
| 网络编程 | 适合高并发 | 需要把协程与网络库(如 Boost.Asio)结合 |
- 异常安全:若协程内部抛出异常,
promise_type::unhandled_exception会被调用,需要自行捕获并处理。 - 资源泄露:协程句柄不自动销毁,必须在
generator等包装类中调用destroy()。 - 编译器支持:目前 GCC 10+、Clang 11+、MSVC 19.28+ 已支持完整协程。建议使用较新的标准库实现。
5. 结语
C++20 的协程让异步编程和生成器等高级模式在语言层面得到统一支持,既保持了 C++ 的性能优势,又显著提升了代码可读性和维护性。虽然协程本身是一个强大的工具,但在实际项目中仍需结合业务需求、性能预算与团队经验做出权衡。希望本文能帮助你快速入门协程,并在自己的项目中灵活运用。