协程(Coroutines)是C++20中一个强大的特性,它让我们可以以一种自然、可读性极高的方式来处理异步编程、生成器以及更复杂的状态机。相比传统的回调和Future,协程的实现更为轻量,性能更佳。下面我们从概念入手,讲解协程的基本构造、关键字和一个小实例,帮助你快速上手。
1. 协程的基本概念
协程是一段可以在运行时暂停和恢复的函数。不同于线程,协程在单线程内完成切换,避免了上下文切换开销。C++协程的关键是 co_await、co_yield 和 co_return,它们分别对应等待、产出和返回值。
- co_await:等待一个可协程对象(awaiter),直到其完成后继续执行。
- co_yield:从协程返回一个值,并暂停执行,随后可以再次 resume。
- co_return:结束协程并返回最终值。
协程函数本身并不返回 void,而是返回一个特殊的 promise 对象,告诉编译器如何处理暂停、恢复、异常和返回值。
2. 协程的核心类型
C++20规定协程函数返回的类型必须满足 Awaitable 的要求。最常见的组合是:
std::future <T> // 用于异步操作,返回 future<T>
std::generator <T> // 用于生成器,返回 generator<T>
在标准库中,std::generator 仍处于实验阶段,但大多数编译器都已支持。若要自己实现一个简易的 generator,可以参考下面的代码。
3. 一个完整的协程生成器示例
下面演示一个 int_generator,它一次产生从 1 开始的自然数序列,直到达到给定上限。
#include <iostream>
#include <coroutine>
#include <exception>
template<typename T>
struct generator {
struct promise_type {
T current_value;
std::exception_ptr exc;
auto get_return_object() {
return generator{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
std::suspend_always initial_suspend() { return {}; } // 立即暂停
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { exc = std::current_exception(); }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void return_void() {}
};
std::coroutine_handle <promise_type> coro;
explicit generator(std::coroutine_handle <promise_type> h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
// 迭代器
struct iterator {
std::coroutine_handle <promise_type> coro;
bool done = false;
iterator(std::coroutine_handle <promise_type> h) : coro(h) {
if (!coro.done()) coro.resume();
}
iterator& operator++() {
if (!coro.done()) coro.resume();
return *this;
}
bool operator!=(const iterator& other) const { return !done && !other.done; }
T operator*() const { return coro.promise().current_value; }
};
iterator begin() { return iterator{coro}; }
iterator end() { return iterator{nullptr}; }
};
// 生成器函数
generator <int> int_generator(int limit) {
for (int i = 1; i <= limit; ++i) {
co_yield i; // 产出值
}
}
int main() {
for (int n : int_generator(10)) {
std::cout << n << ' ';
}
std::cout << '\n';
}
关键点解释
- promise_type 负责协程的生命周期。
initial_suspend与final_suspend控制协程开始和结束时的挂起行为。 yield_value在co_yield时被调用,将当前值存入 promise 并暂停。- 迭代器包装了
std::coroutine_handle,并在operator++时resume协程。 - 主函数中使用范围 for 语法遍历生成器,像普通容器一样使用。
4. 协程的优势与应用场景
- 异步 IO:协程天然适用于网络编程,
co_await与io_context等异步操作配合可写出同步式代码。 - 生成器:如上例,协程可轻松实现惰性序列、无限流、斐波那契数列等。
- 状态机:在游戏编程或渲染管线中,协程可以替代繁琐的状态机逻辑。
- 协作式多任务:通过
co_yield实现轻量级线程切换,适用于实时系统。
5. 常见坑与调试建议
- 没有返回对象:确保
promise_type的get_return_object正确返回。 - 悬空句柄:在异常或提前退出时,记得销毁协程句柄,防止泄漏。
- 多线程协程:标准库的协程并不保证线程安全,若在多线程环境使用,请使用专门的同步机制。
- 编译器支持:GCC、Clang、MSVC 均已实现协程,但请开启
-std=c++20并在必要时链接<experimental/coroutine>。
6. 结语
C++20 协程是一次对语言异步能力的重构,它将复杂的回调、Future 和事件循环模式大幅简化。熟练掌握协程后,你可以写出更易读、可维护且高效的异步代码。希望本篇文章能为你迈向协程世界提供一个清晰的起点。祝编码愉快!