协程就是一个可以挂起执行,稍后再恢复执行的函数。
协程是无栈式的(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#include #include #include #include using namespace std; using namespace std::literals; using callback_t = std::function ; // 异步执行(模拟耗时的计算) 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#include using namespace std; struct Generator { struct promise_type; using handle_t = coroutine_handle ; 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



