抢占式调度由OS强制执行,协程调度由用户代码显式控制;线程切换开销大且不可控,协程轻量但需手动保障公平性与响应性,二者不可混用同一心智模型。

抢占式调度在 C++ 线程中由操作系统强制执行
你写的 std::thread 代码,无论逻辑多“乖”,一旦启动,控制权就交给了 OS 调度器。它随时可能中断你的线程(比如时间片用完、更高优先级线程就绪),保存上下文,切走——你完全没法预测哪条语句之后被切。这不是 C++ 标准规定的,是 Linux sched_rr 或 Windows 线程调度器干的。
常见错误现象:std::this_thread::sleep_for 看似“让出”,其实只是把当前线程置为等待态,不释放 CPU 时间片给同优先级兄弟线程;std::this_thread::yield() 在多数实现里效果微弱,尤其在非争抢场景下几乎没用。
- 别指望靠
yield()实现协作逻辑,它不保证切换,也不保证切给谁 - 高实时性需求(如音频处理)需用
sched_setscheduler配合SCHED_FIFO,但必须 root 权限且极易锁死系统 - 线程栈默认几 MB,频繁创建/销毁开销大,和调度策略无关,但影响你是否敢“多开”
协作式调度只存在于 C++20 协程的用户态逻辑中
C++ 协程(co_await、co_yield)本身不调度,它只提供挂起/恢复的钩子;真正决定“何时切”“切给谁”的,是你写的 awaiter 和 executor(比如一个单线程 io_context 或自定义轮询器)。没有 OS 干预,没有时间片,一切由你代码显式触发。
使用场景:I/O 密集型服务(如 HTTP 客户端并发请求)、状态机驱动逻辑(游戏 AI 行为树)、避免线程阻塞但又不想用回调地狱。
立即学习“C++免费学习笔记(深入)”;
-
co_await后的挂起点才是调度边界,不是每个函数调用都可打断 - 协程帧(coroutine frame)分配在堆上,默认用
operator new,内存碎片风险比线程栈更隐蔽 - 所有协程共享同一线程时,一个协程死循环(比如忘了
co_await)会卡死整个执行器,无抢占兜底
线程与协程调度不可混用同一套心智模型
误以为“协程是轻量线程”是最大陷阱。线程切换涉及 TLB 刷新、内核态陷进、寄存器全量保存;协程切换只是跳转到另一块栈内存 + 恢复几个寄存器,快 10–100 倍,但代价是:你得自己保证公平性、响应性、错误传播路径。
参数差异:线程优先级通过 pthread_setschedparam 设,协程优先级?标准库根本不提供——得你在 await_transform 或调度器里手工插队。
- 调试时看到
std::thread的 native handle 是 OS 级 ID(如 Linux 的 tid),而协程 handle(std::coroutine_handle)只是个指针,gdb 里打不出调用栈全貌 - 线程崩溃会触发
std::terminate或信号(如SIGSEGV),协程崩溃若没在promise_type::unhandled_exception捕获,直接静默终止,连日志都不留 - 混合使用时(比如协程里
join()一个线程),注意线程可能等不到协程恢复——因为协程还在别的线程上跑着
std::jthread 和 co_await 的组合并不自动带来“协作+抢占”混合调度
std::jthread 只是带自动 join() 的 std::thread,它没给协程加任何调度能力。想让协程在多个线程间迁移(比如 I/O 完成后切回主线程执行回调),得靠 executor 显式提交,比如 asio::post(io_ctx, []{ co_await ... }) ——但这不是调度策略切换,只是任务分发。
性能影响:跨线程 resume 协程要加锁保护其状态机,比同线程内 resume 慢;而线程间通信(如 std::mutex)在协程里用错会导致死锁(因为 co_await 不释放锁)。
- 别在
co_await表达式里直接写std::mutex::lock(),改用asio::awaitable_mutex这类支持 await 的封装 -
std::stop_token对线程有效,对协程仅能通知取消,实际是否响应、何时响应,全看awaiter是否检查stop_requested() - 协程异常传播路径和线程不同:线程异常未捕获会终止进程;协程异常若没在
promise_type::unhandled_exception处理,只会让该协程结束,不影响其他协程
co_await、每次 thread::join、每个没设超时的 mutex::lock,都在悄悄修改调度行为的确定性。

















