You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

大量使用原子变量替代互斥锁后出现线程阻塞问题求助

大量使用原子变量替代互斥锁后出现线程阻塞问题求助

嘿,我仔细啃了你的代码和问题描述,一眼就揪出了核心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;
}

关键修改点说明

  1. 用批次号替代二元开关:CurrentBatch是递增的整数,每个批次主线程都会把它加1,线程通过对比自己上次处理的批次号,就能准确判断有没有新任务,不会被其他线程抢先关掉信号。
  2. 确保所有线程都执行任务:每个线程都会检测到新批次号,执行任务后再递减ActiveThreads,这样主线程等待的ActiveThreads一定会降到0,不会出现锁死。
  3. 添加yield()减少空转:原来的循环会一直占用CPU空转,加std::this_thread::yield()可以让线程暂时让出CPU,提升系统整体性能。

另外啰嗦一句:原子变量虽然能保证单个操作的线程安全,但逻辑上的同步需要正确设计,不能只依赖原子性。你之前遇到的“换检查变量就正常”的情况,其实是不同变量的竞争时机巧合导致的临时正常,但本质上都是同步逻辑的漏洞在作祟。

备注:内容来源于stack exchange,提问作者Adrian E

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 08:48:07