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

tbb::task_group与std::barrier间偶发死锁问题求助

死锁原因分析:TBB task_group + std::barrier 结合NUMA隔离场景

问题场景

我在应用中使用Intel OneAPI TBB实现并行化,通过task_arena将工作隔离在NUMA节点上,每个NUMA节点的并行任务由task_group管理。部分任务需要与其他NUMA节点同步,尝试用std::barrier实现,但测试时会偶发死锁,有时又正常。

复现代码:

#include <oneapi/tbb/info.h>
#include <oneapi/tbb/task_arena.h>
#include <oneapi/tbb/task_group.h>

#include <barrier>

using namespace oneapi;

int main(int argc, char* argv[]) {
    std::vector<tbb::numa_node_id> numa_indexes = tbb::info::numa_nodes();
    std::vector<tbb::task_arena> arenas(numa_indexes.size());
    std::vector<tbb::task_group> task_groups(numa_indexes.size());

    std::barrier barrier(numa_indexes.size());
    
    for (unsigned j = 0; j < numa_indexes.size(); j++) {
        arenas[j].initialize(tbb::task_arena::constraints(numa_indexes[j]));
    }

    for (int i = 0; i < 10000; ++i) {
        for (unsigned j = 0; j < numa_indexes.size(); j++) {
                arenas[j].execute([&task_groups, &barrier, j]() {
                task_groups[j].run([&barrier]() { barrier.arrive_and_wait(); });
            });
        }

        for (unsigned j = 0; j < numa_indexes.size(); j++) {
            arenas[j].execute([&task_groups, j]() { task_groups[j].wait(); });
        }
    }
}

注:若直接在arenas[j].execute()中调用barrier.arrive_and_wait()(不使用task_group)则运行正常,但这样无法等待特定任务完成。当前使用版本为oneTBB 2021.9.0、gcc 13.1.0。

死锁核心原因

死锁源于task_group的任务调度逻辑与std::barrier的同步机制,在TBB arena的NUMA隔离约束下形成了循环等待:

  1. task_group任务的延迟执行特性
    调用task_groups[j].run()时,任务不会立即在当前arena线程上执行,而是被放入该arena的任务队列,等待arena线程池中的空闲线程拾取。而执行arenas[j].execute()的线程在提交任务后,会直接退出execute()上下文,回到arena线程池。

  2. wait()操作的阻塞风险
    后续调用arenas[j].execute([&task_groups, j]() { task_groups[j].wait(); })时,执行这个lambda的arena线程会被wait()阻塞,直到task_group中的所有任务完成。如果此时该arena的线程池中没有其他空闲线程,那么之前提交到队列中的barrier.arrive_and_wait()任务就永远无法被执行。

  3. 循环等待链的形成
    假设存在两个NUMA节点A和B:

    • 节点A的arena线程提交task_group任务后,立即进入wait()阻塞,占用了该arena的唯一可用线程;
    • 节点A的barrier.arrive_and_wait()任务因无空闲线程无法执行,无法完成barrier的arrive操作;
    • 节点B的任务已经到达barrier,但因A的任务未到达,会一直阻塞在barrier.arrive_and_wait();
    • 最终节点A的wait()在等任务完成,任务在等barrier放行;节点B的任务在等A的任务到达barrier,形成循环等待,触发死锁。
  4. 无task_group时正常的原因
    直接在arenas[j].execute()中调用barrier.arrive_and_wait()时,执行操作的就是当前arena的线程,会直接完成barrier的arrive动作,不存在任务排队、线程被阻塞的矛盾,因此不会死锁。

偶发死锁的随机性

死锁并非每次出现,是因为arena线程池的调度存在随机性:如果提交task_group任务后,arena还有其他空闲线程可以拾取并执行barrier.arrive_and_wait(),流程就能正常完成;但如果所有线程都被wait()阻塞,就会触发死锁。

内容的提问来源于stack exchange,提问作者Daniel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 00:56:18