协程是用户态控制流切换机制,不替代线程;线程由内核调度,协程需运行于线程之上,适用于IO密集型场景,而CPU密集型应选线程。

协程不是线程,也不替代线程
协程(co_await、co_return)是用户态的控制流切换机制,不涉及内核调度;线程是操作系统调度的基本单位,有独立栈、寄存器上下文和内核态资源。两者根本不在同一抽象层——把协程当“轻量线程”用,迟早掉坑里。
典型错误现象:std::jthread 启动一个协程函数却没 co_await 它的返回值,结果函数体没执行就结束了;或者在协程里直接调用阻塞 IO(如 fread),整个线程被卡住,所有挂起的协程全停摆。
- 协程必须运行在某个线程上,它自己不“拥有”线程
- 一个线程可顺序/并发驱动多个协程(靠
awaiter和调度器配合) - 协程切换开销约几十纳秒,线程切换通常在微秒级,但协程无法利用多核——除非你手动把它分发到不同线程
什么时候该用 std::thread,什么时候该用 co_await
看任务性质:CPU 密集型、需要真正并行,选线程;IO 密集型、等待多、逻辑串行但不想阻塞线程,协程更合适。
使用场景举例:
立即学习“C++免费学习笔记(深入)”;
- 读 10 个文件并解析:用
std::thread+ 线程池,每个线程干一个完整任务 - 处理 1000 个 HTTP 请求(每个要等网络响应):用协程 + 异步 IO 库(如
libcurl的异步接口或boost::asio的async_read),单线程就能高效吞吐 - 混合场景(比如请求后要跑一段 CPU 计算):协程里用
co_await切到线程池执行计算(例如thread_pool_executor),再切回来
参数差异注意:std::thread 构造时立即抢占资源;而协程函数(如 task<int> fetch_data())调用后只生成一个未启动的 task 对象,直到被 co_await 或显式调度才执行。
std::jthread 和 task 协程库不能混着“等”
常见错误:用 jthread.join() 等一个协程函数结束,结果永远等不到——因为协程函数返回的是 task,不是线程句柄。
性能与兼容性影响:
-
std::jthread是 C++20 标准,编译器支持好(GCC 10+、Clang 12+、MSVC 19.30+) - 协程需开启编译选项(
-fcoroutines//await),且标准库支持仍不统一;std::generator、std::task还没进标准,得靠libunifex或cppcoro - 调试困难:协程栈帧不进传统调试器调用栈,GDB/LLDB 对
co_await断点支持有限
实操建议:新项目若需高并发 IO,优先封装协程 + 异步 IO;若只是并行计算或已有线程池,别硬套协程——加一层 co_await thread_pool.submit(...) 反而增加心智负担和间接成本。
异步方案选型绕不开 executor 和取消机制
协程本身不提供调度,co_await 后续代码在哪执行,取决于 awaitable 返回的 awaiter 如何定义 await_suspend。没 executor,协程就是一坨没法跑的语法糖。
容易被忽略的点:
- 忘记处理取消:协程可能被外部中断(如超时),但
std::stop_token在协程中需手动接入co_await stop_callback,否则取消信号被忽略 - 误以为
co_await自动线程安全:多个协程并发修改同一变量,照样需要std::mutex或无锁结构 - 把协程当回调嵌套解药,结果写出深度嵌套的
co_await链,可读性比回调还差
真实复杂点在于:你得同时理解 IO 模型(epoll/iocp)、调度策略(work-stealing 还是 single-threaded)、以及协程恢复点的内存可见性——这些不会因为用了 co_await 就自动变简单。



















