协程是用户态协作式调度,非内核线程;C++20协程应配合轻量调度器而非std::thread模拟,关键在协程帧生命周期管理与安全resume。

协程不是线程,C++20 std::coroutine 不能靠 std::thread 模拟
直接用多线程“实现协程”是常见误解。协程本质是**用户态协作式调度**,而 std::thread 是内核线程、抢占式、开销大。强行用线程 + sleep/condition_variable 模拟协程,只会得到低效、难调试的伪协程,还丧失协程的核心优势:轻量切换、栈保存、挂起恢复语义。
真正该走的路是:用 C++20 原生协程设施,配合一个轻量调度器(比如单线程事件循环),而不是套多线程壳子。
用 std::coroutine_handle 手写最简可运行协程
不依赖第三方库,只用标准库,可以写出能挂起、恢复、带返回值的最小协程。关键不是“多线程”,而是理解协程对象生命周期和调度时机。
-
co_await的暂停点必须由你提供的awaitable类型控制,它要实现await_ready()、await_suspend()、await_resume() -
await_suspend()接收std::coroutine_handle,这就是你决定“什么时候恢复它”的入口——可以存起来、延后调用、甚至交给别的线程(但注意线程安全) - 协程帧(frame)默认在堆上分配,需手动管理或使用
promise_type::get_return_object_on_allocation_failure处理失败
示例:一个立即挂起、100ms 后恢复的协程:
立即学习“C++免费学习笔记(深入)”;
struct DelayAwaiter {
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 简单用 std::thread + detach 演示(仅作示意,生产勿用)
std::thread([h]{
std::this_thread::sleep_for(100ms);
h.resume(); // 注意:此处 resume 可能跨线程,h 必须保证存活
}).detach();
}
void await_resume() const noexcept {}
};
task delay_100ms() {
co_await DelayAwaiter{};
co_return;
}
为什么别在 await_suspend 里直接 std::thread + join
这样做会导致协程函数被阻塞在当前线程,彻底失去异步意义,且极易死锁:
-
await_suspend返回void表示“我自行负责恢复”,若你在里面join(),那整个co_await就退化成同步等待 - 若多个协程同时
join同一线程,或线程依赖其他协程资源,就会卡死 -
std::thread对象离开作用域未join()或detach()会 terminate - 更严重的是:
coroutine_handle在协程结束时自动销毁,如果线程还在用它,就是悬垂指针
真要跨线程调度?先解决句柄生命周期和同步
如果你确实需要把协程从一个线程移交到另一个线程执行(比如 I/O 完成后在 IO 线程恢复),核心不是“怎么启线程”,而是:
- 用线程安全队列(如
moodycamel::ConcurrentQueue或带锁的std::queue)传递std::coroutine_handle - 确保
coroutine_handle在入队前未被销毁(即 promise 对象仍在堆上活着) - 恢复前检查
h.done() == false,避免重复 resume 或已结束协程 - 避免在析构函数中 resume 协程——promise 析构可能正在发生,此时 resume 有未定义行为
一句话:协程调度的复杂性不在“启线程”,而在**谁拥有协程帧、谁负责释放、何时可安全 resume**。这些比线程创建本身难得多。


















