大量使用原子变量替代互斥锁后出现线程阻塞问题求助
大量使用原子变量替代互斥锁后出现线程阻塞问题求助
嘿,我仔细啃了你的代码和问题描述,一眼就揪出了核心bug——你的批次触发逻辑有漏洞,导致只有少数线程能完成任务并更新ActiveThreads,主线程自然就永远等不到它变成0了!
咱们先拆解下你当前代码的问题根源:
- 当主线程调用
ProcessSamples时,会把SamplesReady设为1,然后坐等ActiveThreads降到0。 - 但你的
WaitingRoom函数里,只要有一个线程抢到if (SamplesReady.load())的分支,立刻就把SamplesReady设为0了。这直接堵死了其他线程的入口——它们再检查时SamplesReady已经是0,根本进不去任务分支,也就不会执行ActiveThreads.fetch_sub(1)。 - 举个例子:你开了4个线程,主线程把
ActiveThreads设为4,但只有1个线程能完成任务并把它减到3,剩下3个线程卡在循环里啥也不干,主线程就一直等ActiveThreads变0,可不就锁死了嘛!
那怎么改呢?关键是要让每个批次的任务能被所有线程都触发一次,而不是让第一个线程直接把触发信号关掉。这里可以把二元的SamplesReady改成批次计数器,用递增的批次号来触发线程,而不是用0/1的开关:
修改后的核心代码示例
#include <iostream> #include <thread> #include <atomic> #include <cmath> std::atomic_int ProcessingSamples; std::atomic_int CurrentBatch; // 用批次号替代原来的SamplesReady std::atomic_int ActiveThreads; std::thread ThreadCalls[8]; int NumberOfThreads; void PretendToProcessSamples() { double StupidMath; StupidMath = 1.0; StupidMath *= sqrt(StupidMath); StupidMath += tanh(StupidMath); } void WaitingRoom() { int my_last_batch = 0; while (ProcessingSamples.load()) { // 等待新批次到来:如果当前批次号和我上次处理的一样,就等 while (CurrentBatch.load() == my_last_batch) { std::this_thread::yield(); // 让出CPU,减少空转浪费 } // 拿到新批次号,标记为已处理 my_last_batch = CurrentBatch.load(); // 执行任务 PretendToProcessSamples(); // 任务完成,递减活跃线程数 ActiveThreads.fetch_sub(1); } } void ProcessSamples(int batch_idx) { // 重置活跃线程数为总线程数(每个批次所有线程都要参与) ActiveThreads.store(NumberOfThreads); // 更新批次号,触发所有线程执行新任务 CurrentBatch.store(batch_idx + 1); // 等待所有线程完成当前批次 while (ActiveThreads.load() != 0) { std::this_thread::yield(); } } void StartBackGroundThreads(int NbrOfThreads) { if (NbrOfThreads) { NumberOfThreads = NbrOfThreads; ProcessingSamples.store(1); CurrentBatch.store(0); // 初始批次号设为0 for (int z = 0; z < NumberOfThreads; z++) { ThreadCalls[z] = std::thread(WaitingRoom); } } } int main() { StartBackGroundThreads(4); for (int i = 0; i < 4000; i++) ProcessSamples(i); ProcessingSamples.store(0); for (int z = 0; z < NumberOfThreads; z++) { ThreadCalls[z].join(); } std::cout << "所有批次处理完成!" << std::endl; return 0; }
关键修改点说明
- 用批次号替代二元开关:
CurrentBatch是递增的整数,每个批次主线程都会把它加1,线程通过对比自己上次处理的批次号,就能准确判断有没有新任务,不会被其他线程抢先关掉信号。 - 确保所有线程都执行任务:每个线程都会检测到新批次号,执行任务后再递减
ActiveThreads,这样主线程等待的ActiveThreads一定会降到0,不会出现锁死。 - 添加
yield()减少空转:原来的循环会一直占用CPU空转,加std::this_thread::yield()可以让线程暂时让出CPU,提升系统整体性能。
另外啰嗦一句:原子变量虽然能保证单个操作的线程安全,但逻辑上的同步需要正确设计,不能只依赖原子性。你之前遇到的“换检查变量就正常”的情况,其实是不同变量的竞争时机巧合导致的临时正常,但本质上都是同步逻辑的漏洞在作祟。
备注:内容来源于stack exchange,提问作者Adrian E
相关产品推荐
相关产品推荐

