最稳妥方案是std::condition_variable配合std::mutex,用while循环等待条件、notify_all唤醒并由条件判断决定执行权,避免虚假唤醒和信号丢失。

用 std::condition_variable 控制轮转顺序最稳妥
多数场景下,靠轮询或手动挂起线程容易出错,std::condition_variable + std::mutex 是 C++ 标准库中兼顾可读性与可靠性的首选。它让线程真正“等待条件成立”,而非忙等或依赖 Windows 特定 API。
关键点在于:用一个共享计数器(如 count)或状态变量(如 current_char),每个线程只在轮到自己时才输出,并通过 cv.notify_all() 唤醒所有等待者——由条件判断决定谁真正继续执行。
- 必须用
while而非if包裹cv.wait(),防止虚假唤醒 - 避免在
wait的 lambda 中捕获局部变量(如传入的ch),应捕获[&]或显式传值 - 退出条件(如打印满 100 次)要放在条件判断里,否则可能卡死在
wait -
notify_all()比notify_one()更安全:无法预知哪个线程该被唤醒时,通知全部更稳妥
std::atomic 配合内存序能避免锁,但顺序逻辑得自己兜底
纯原子操作(比如 std::atomic<int></int>)本身不提供等待能力,所以“顺序输出”必须靠轮询 + 内存序约束实现。这适合对延迟敏感、且能接受 CPU 空转的轻量场景。
常见写法是让每个线程检查全局原子变量是否等于自己的预期值,匹配则输出并更新为下一个值。但要注意:
- 不能只用
memory_order_relaxed—— 否则其他线程可能看不到更新,陷入死等 - 至少一个线程用
memory_order_release写,另一个用memory_order_acquire读,才能建立happens-before关系 - 轮询中必须加
std::this_thread::yield()或短休眠,否则会吃满 CPU - 这种方案在 4 个以上线程时,竞争变激烈,性能反而不如带条件变量的版本
std::osyncstream 解决的是“输出不乱”,不是“顺序执行”
很多人误以为 std::osyncstream(C++20)能控制多线程的执行顺序,其实它只保证单次写入的原子性和缓冲区刷新的线程安全。也就是说:它能让 “AB” 不被拆成 “A” 和 “B” 分别被不同线程打断,但不能保证线程 A 一定在 B 之前运行。
如果你的需求只是“每行输出完整、不交错”,直接用 std::osyncstream(std::cout) 就够了;但若要求 ABC 必须严格循环出现,它完全不参与调度控制,必须搭配条件变量或信号量使用。
- 每个
std::osyncstream对象独占一个std::syncbuf,析构时自动刷缓存——别提前销毁对象 - 它不替代同步原语,只是把“输出”这件事从竞态中隔离出来
- 在 Windows 上某些标准库实现对
std::osyncstream支持不全,实测前建议先验证__cpp_lib_syncstream宏定义
Windows 平台用 CreateEvent + WaitForSingleObject 易踩信号丢失坑
原生 Win32 方案看似直观,但实际容易因事件初始化、首次唤醒时机、循环依赖等问题导致线程卡住或跳步。核心问题在于:事件对象默认是手动重置(FALSE),SetEvent 只唤醒一个等待者,而 WaitForSingleObject 返回后不会自动重置——如果唤醒后没立刻消费,信号就丢了。
- 务必在
CreateEvent(NULL, FALSE, FALSE, NULL)中明确指定第二个参数为FALSE(手动重置),否则多个线程可能同时被唤醒 - 第一个线程启动前,必须先
SetEvent(handler[0]),否则所有线程都在等一个永远不会来的信号 - 不要让线程 A 等待线程 D 的事件,形成环形依赖——必须用模运算(如
(tmp + 1) % 4)解耦 - 主线程调用
WaitForMultipleObjects等待所有线程结束时,传入的句柄数组必须和创建顺序一致,否则索引错位
真正难的不是让四个线程轮流输出,而是让它们在任意调度压力下都稳定不出错。条件变量方案看似多几行代码,但它的等待/唤醒语义清晰,边界条件可控,比轮询或事件驱动更适合长期运行的服务程序。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











