C++20 协程是标准库的一大创新,为编写异步、延迟执行代码提供了新的语法糖。协程通过 co_yield、co_await 和 co_return 三个关键字实现,背后使用的是一种名为 awaiter 的概念。本文将从协程的基本原理、关键特性以及常见使用场景三方面进行阐述,并给出一个简易的协程示例,帮助你快速上手。
1. 协程的基本概念
协程是一种可暂停和恢复的函数。与普通函数相比,协程可以在执行过程中挂起,并在稍后继续执行,而不是一次性完成所有计算。协程的核心是 挂起点(suspension point)。在 co_await、co_yield 或 co_return 时,协程会进入挂起状态,并将控制权返回给调用者。随后,调用者可以决定何时恢复协程。
协程在 C++20 标准中通过 C++ coroutine library 实现。其实现主要涉及两个层面:
- 编译器层面:编译器将协程函数展开为一个状态机,将每个挂起点转换为一个
yield(或者await)的状态点。 - 运行时层面:通过
std::experimental::coroutine_handle或std::coroutine_handle对象管理协程的生命周期。
2. 核心关键字与语法
| 关键字 | 作用 | 例子 |
|---|---|---|
co_yield |
产生一个值并挂起协程,类似于生成器 | co_yield value; |
co_await |
等待一个 awaitable 对象完成,并挂起协程 | auto result = co_await future; |
co_return |
结束协程并返回一个值 | co_return final_value; |
2.1 Awaitable 类型
要在协程中使用 co_await,必须提供一个 awaitable 对象。一个对象被视为 awaitable,需满足以下接口:
struct MyAwaitable {
bool await_ready() noexcept; // 如果可以立即完成,返回 true
void await_suspend(std::coroutine_handle<>) noexcept; // 挂起时调用
auto await_resume() noexcept; // 恢复后返回结果
};
3. 协程的实现细节
协程的展开过程类似于编译器将函数拆分成若干段,每段之间由 if 语句连接,形成一个状态机。具体步骤如下:
- 生成状态机类:编译器会生成一个内部类,用来保存协程状态(局部变量、返回点等)。
- 生成
promise_type:每个协程都需要一个promise_type,负责管理协程的生命周期,返回值,以及错误处理。 - 生成
coroutine_handle:通过 `coroutine_handle ` 对象,调用者可以获取协程的句柄,用于恢复、销毁协程。
4. 常见使用场景
- 异步 I/O:协程与
std::async、std::future结合,能够写出像同步代码一样的异步逻辑。 - 生成器:利用
co_yield,实现惰性序列生成,类似 Python 的生成器。 - 任务调度:协程可以与事件循环结合,用于实现协作式多任务。
5. 简易协程示例
下面给出一个使用协程实现整数序列生成器的完整示例。
#include <iostream>
#include <coroutine>
#include <optional>
// 生成器的 Promise 类型
struct IntGenerator {
struct promise_type {
int current_value = 0;
auto get_return_object() {
return IntGenerator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
// co_yield 调用时触发
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
void return_void() {}
};
std::coroutine_handle <promise_type> handle;
// 迭代器
struct iterator {
std::coroutine_handle <promise_type> h;
iterator(std::coroutine_handle <promise_type> h) : h(h) {}
iterator& operator++() {
h.resume();
return *this;
}
bool operator!=(const iterator& other) const { return h != other.h; }
int operator*() const { return h.promise().current_value; }
};
iterator begin() { return iterator(handle); }
iterator end() { return iterator(nullptr); }
};
IntGenerator count_up_to(int n) {
for (int i = 0; i <= n; ++i)
co_yield i;
}
int main() {
for (int x : count_up_to(5))
std::cout << x << ' '; // 输出: 0 1 2 3 4 5
}
代码说明
IntGenerator定义了一个协程生成器,并提供了begin/end迭代器接口。promise_type中的yield_value在co_yield触发时被调用,保存当前值。count_up_to函数通过co_yield生成 0~n 的整数序列。
6. 性能与注意事项
- 栈占用:协程本身不需要额外栈空间,只有局部变量会被保存在
promise_type中。 - 异常传播:
promise_type::unhandled_exception负责捕获协程内部抛出的异常。 - 多线程:协程本身是线程安全的,但
coroutine_handle的恢复与销毁需在同一线程或使用同步机制。
7. 结语
C++20 协程为语言增添了强大的异步编程能力。虽然刚开始学习时可能会感觉概念繁琐,但随着实际项目中的应用,你会发现协程极大地简化了异步代码的结构。建议从生成器、异步 I/O 等简单场景入手,逐步扩展到更复杂的协程框架。祝你在 C++ 编程的道路上玩得开心!