前言
之前看项目碰到了用协程实现的线程池调度系统,看的人一脸懵,我做项目都是基于Qt框架的吗,习惯于要做什么任务,就把任务丢进任务队列,调用的是qt自带的全局线程池,同事实现的线程池可以自定义线程名,写的挺高级的,就到现在我老想着掌握协程。
协程的运行机制
协程分为有栈协程和无栈协程,这个就不多说了,我们常用的c++协程就是无栈协程,其实可以理解为他是一个特殊的函数,普通的函数运行到底就结束了,线性执行,协程可以挂起中断,外部处理完了再resume唤醒执行下一步。
协程的组成
协程状态coroutine state
堆上new一个空间,存放协程的状态。状态存放的是协程运行的参数变量,运行的状态,挂起的断点等,由系统管理。
承诺对象promise
承诺体的表现形式为return::promise_type,返回值为return。
承诺对象结构体内要实现若干接口,用于辅助协程,构造协程函数返回值,提交co_yield,co_return返回值。明确协程启动阶段是否挂起以及异常处理。
接口如下
- get_return_object()用于生成协程函数的返回对象
- initial_suspend()用于明确初始化后协程函数的执行行为,返回等待体(awaiter),用co_wait调用其返回值,如果是std::suspend_always,表示初始化后直接挂起,要执行协程函数体要用resume唤醒协程。如果返回的是std::suspend_never,表示初始化后就直接开始执行协程函数。
- return_value(T v)用co_return会调用这个函数,可以保存co_return的返回结果。
- yield_value(T v)用co_yield会调用这个函数,可以保存co_yield的返回结果,返回std::suspend_always会挂起,返回std::suspend_never不会挂起。
- final_suspend() noexcept 协程的退出接口,调用以后如果返回suspend_never,会销毁生成器对象如果返回的是suspend_always,则需要手动调用销毁句柄的handle.destroy()销毁,注意返回suspend_always不会挂起协程。
协程句柄(coroutine handle)
协程句柄是协程的标识,用于协程的挂起恢复,销毁的句柄。
表现形式是std::coroutine_handle<promise_type>,其模板参数为承诺体类型,句柄有几个重要函数。
- resume()唤醒协程
- done()判断协程是否已经完成,返回false表示还没有完成。
- std::coroutine_handle<promise_type>::from_promise通过promise获取其句柄
- std::coroutine_handle<promise_type>::promise()通过句柄获取其承诺体对象promise
等待体(awaiter)
co_wait()会调用一个等待体对象awaiter,awaiter内部有3个接口,具体做什么要根据co_wait决定。
bool await_ready()等待体是否准备好了。返回false表示没有准备好,表示协程还没准备好,立即调用await_suspend。返回true表示准备好了。
auto await_suspend(std::coroutine_handle<>handle)如果要挂起调用这个接口,handle参数就是调用等待体的协程,返回参数有3种
- bool true 立即挂起,false不挂起
- void同上面的true
- 协程句柄 立即恢复句柄对应的协程运行
auto await_resume()协程恢复时调用的接口,返回值作为co_wait的返回值。
等待体的特化类型
等待体有两个特化类型
- std::suspend_never不挂起的特化等待体类型
- std::suspend_always挂起的特化等待体类型
例子
例1
使用co_yield的方式逐步打印5个数字,初步理解协程的写法
- 定义了一个模板类 Generator<T>该类用于生成一系列类型为 T 的值。它内部实现了协程相关的 promise_type,并通过 std::coroutine_handle 管理协程的生命周期。
- 实现了 next() 方法该方法用于获取下一个生成的值。如果协程还未结束,则恢复协程并返回当前值,否则返回 std::nullopt。
- 定义了一个生成器函数 counter(int max)这是一个协程函数,使用 co_yield 依次生成从 0 到 max-1 的整数。
- 主函数 main()
- 创建一个生成器 counter(5),用于生成 0 到 4 的整数。
- 通过循环调用 gen.next(),每次获取一个值并输出,直到生成器结束。
cpp
#include <coroutine>
#include <iostream>
#include <optional>
template<typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
struct promise_type {
T current_value;
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
Generator get_return_object() {
return Generator{ handle_type::from_promise(*this) };
}
};
handle_type coro;
Generator(handle_type h) : coro(h) {}
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; }
~Generator() { if (coro) coro.destroy(); }
std::optional<T> next() {
if (!coro.done()) {
coro.resume();
if (!coro.done())
return coro.promise().current_value;
}
return std::nullopt;
}
};
Generator<int> counter(int max) {
for (int i = 0; i < max; ++i) {
co_yield i;
}
}
int main() {
auto gen = counter(5);
while (auto v = gen.next()) {
std::cout << "Value: " << *v << std::endl;
}
return 0;
}
例2
这个是用co_wait模拟异步等待
• SimpleAwaiter 是一个自定义的可等待对象,实现了 await_ready、await_suspend 和 await_resume。
• async_func 是一个协程函数,使用 co_await 等待 SimpleAwaiter 完成,并获取结果。
• Task 是协程的返回类型,简化实现,仅用于演示。
cpp
#include <coroutine>
#include <iostream>
// 简单的可等待对象
struct SimpleAwaiter {
bool await_ready() const noexcept { return false; } // 是否准备好,false表示需要挂起
void await_suspend(std::coroutine_handle<>h) const noexcept {
std::cout << "协程挂起,等待事件完成..." << std::endl;
h.resume(); // 立即恢复协程
}
int await_resume() const noexcept { // 唤醒后返回的值
std::cout << "协程恢复,事件完成!" << std::endl;
return 42;
}
};
// 协程返回类型
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
// 协程函数,使用 co_await
Task async_func() {
int result = co_await SimpleAwaiter{};
std::cout << "co_await 得到结果: " << result << std::endl;
}
int main() {
async_func(); // 启动协程
return 0;
}
例3
很多博客说协程不可以在另一个线程恢复,这个是错的,但实现前提要自己保证线程安全,这里演示跨线程唤醒协程
cpp
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
// 可等待对象,每次 co_await 都会在新线程恢复协程
struct ThreadResumeAwaiter {
int count;
ThreadResumeAwaiter(int c) : count(c) {}
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const {
std::jthread([h, c = count] {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟异步
std::cout << "[线程 " << std::this_thread::get_id() << "] 唤醒协程,第 " << c << " 次" << std::endl;
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task async_func() {
for (int i = 1; i <= 3; ++i) {
std::cout << "[线程 " << std::this_thread::get_id() << "] 协程挂起,第 " << i << " 次" << std::endl;
co_await ThreadResumeAwaiter(i);
}
std::cout << "[线程 " << std::this_thread::get_id() << "] 协程结束" << std::endl;
}
int main() {
async_func();
// 主线程等待所有子线程完成
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}

这就是协程基础,那些高级应用我再研究研究。