1. 简介
协程就是一个可以挂起执行,稍后再恢复执行的函数。
C++20 中的协程是无栈式的(stackless),协程挂起时会返回到调用者或恢复者,且恢复执行所需的数据会分开存储,而不放在栈上。
只要一个函数包含 co_await
、co_yield
或 co_return
关键字,则它就是协程。
一个协程关联有如下对象:
- promise 对象:在协程内部操作此对象,协程通过它提交返回值或异常;
- 协程句柄:在协程外部操作此对象,用于恢复协程的执行或销毁协程帧;
- 协程状态:在堆上分配的内部状态。
2. co_await 操作符
co_await
会挂起当前协程,然后返回到协程调用者或恢复者(通过协程句柄来恢复该协程)。
co_await expr;
其中,expr
需要是一个 awaiter。
co_await
包含的主要操作是:
- 首先调用
awaiter.await_ready()
,如果其返回值为false
,则挂起当前协程(填充协程状态);然后, - 调用
awaiter.await_suspend(handle)
,其中handle
是一个协程句柄,如果此函数返回void
,则立即返回到协程的调用者或恢复者;如果awaiter.await_suspend(handle)
抛出异常,则会捕获该异常,然后恢复协程,接着重新抛出该异常; - 其他函数可以通过
handle.resume()
来恢复协程,协程恢复之后会执行awaiter.await_resume()
,其返回值就是co_await expr
的执行结果,接着继续执行协程体; - 当协程体再次遇到
co_await
或执行完毕时,它会返回到恢复者继续执行。
值得注意的是,可以将协程句柄传递到其他线程,然后在另一个线程中恢复协程的执行!
3. 协程执行流程
协程的大致执行流程如下:
- 构造一个 promise 对象;
- 调用
promise.get_return_object()
,并在协程首次挂起时将调用结果返回到调用者; - 执行
co_await promise.initial_suspend()
; - 当
co_await promise.initial_suspend()
恢复时,开始执行协程体; - 当执行到协程体中的
co_return;
或协程体尾部时,调用promise.return_void()
; - 当执行到协程体中的
co_return expr;
时,调用promise.return_value(expr)
; - 如果协程以一个未捕获的异常终止时,会调用
promise.unhandled_exception()
; - 最后执行
co_await promise.final_suspend()
。
当通过 co_return
或协程句柄来销毁协程状态时,它会执行如下动作:
- 析构 promise 对象;
- 析构协程参数的拷贝;
- 释放协程状态所占内存;
- 返回到协程调用者或恢复者。
#include <iostream>
#include <chrono>
#include <coroutine>
#include <thread>
#include <functional>
using namespace std;
using namespace std::literals;
using callback_t = std::function<void(int)>;
// 异步执行(模拟耗时的计算)
void asyncAdd(int v, callback_t cb) {
thread t([v, cb]() {
this_thread::sleep_for(5ms);
int result = v + 100;
cb(result);
});
t.detach();
}
// 协程的返回值类型
struct Task {
private:
Task() {}
public:
struct promise_type {
Task get_return_object() {
return Task();
}
suspend_never initial_suspend() {
return suspend_never{};
}
suspend_never final_suspend() noexcept {
return suspend_never{};
}
void return_void() {}
void unhandled_exception() {
terminate();
}
};
};
// co_await 操作数的类型
class AddAwaitable {
public:
AddAwaitable(int initValue)
: m_init(initValue), m_result(0) {}
bool await_ready() {
return false;
}
// 调用异步函数
void await_suspend(std::coroutine_handle<> handle) {
auto cb = [handle, this](int value) mutable {
m_result = value;
handle.resume();
cout << "after resume.\n";
};
asyncAdd(m_init, cb);
}
int await_resume() {
return m_result;
}
private:
int m_init;
int m_result;
};
Task addByCoroutine(int v) {
cout << "tid1: " << this_thread::get_id() << '\n';
int ret = co_await AddAwaitable(v);
cout << "tid2: " << this_thread::get_id() << '\n';
cout << "result = " << ret << '\n';
co_return;
}
int main() {
Task task(addByCoroutine(200));
this_thread::sleep_for(1s);
return 0;
}
tid1: 2896
tid2: 1140
result = 300
after resume.
其中,
struct suspend_never {
constexpr bool await_ready() const noexcept {
return true;
}
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
struct suspend_always {
constexpr bool await_ready() const noexcept {
return false;
}
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
4. co_yield 关键字
co_yield expr
等价于,
co_await promise.yield_value(expr)
例子:数字生成器
#include <iostream>
#include <coroutine>
using namespace std;
struct Generator {
struct promise_type;
using handle_t = coroutine_handle<promise_type>;
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
~Generator() {
if (m_handle) {
m_handle.destroy();
}
}
struct promise_type {
int value;
auto get_return_object() {
return Generator(handle_t::from_promise(*this));
}
auto initial_suspend() {
return suspend_always();
}
auto final_suspend() noexcept {
return suspend_always();
}
void return_void() {}
auto yield_value(int v) {
value = v;
return suspend_always();
}
void unhandled_exception() {
terminate();
}
};
bool next() {
if (m_handle) {
m_handle.resume();
return !m_handle.done();
}
return false;
}
int value() const {
return m_handle.promise().value;
}
private:
Generator(handle_t h)
: m_handle(h) {}
private:
handle_t m_handle;
};
Generator f(int n) {
int value = 1;
while (value <= n) {
co_yield value++;
}
}
int main() {
Generator g(f(10));
while (g.next()) {
cout << g.value() << ' ';
}
cout << '\n';
}
1 2 3 4 5 6 7 8 9 10