【问题标题】:Deadlock when combining Python with TBBPython 与 TBB 结合时的死锁
【发布时间】:2020-01-25 14:30:29
【问题描述】:

Here 我想提供一个完整的测试用例,显示一个简单的 TBB parallel_for 构造导致 Python 应用程序死锁。 Python 前端使用 pybind11 与 TBB 后端相结合:

void backend_tbb(vector<int>& result, std::function<int (int)>& callback)
{
    int nthreads = tbb::task_scheduler_init::default_num_threads();
    const char* cnthreads = getenv("TBB_NUM_THREADS");
    if (cnthreads) nthreads = std::max(1, atoi(cnthreads));

    tbb::task_group group;
    tbb::task_arena arena(nthreads, 1);
    tbb::task_scheduler_init init(nthreads);

    group.run( [&] {
        tbb::parallel_for(tbb::blocked_range<int>(0, result.size()),
            [&](const tbb::blocked_range<int>& range)
        {
            for (int i = range.begin(); i != range.end(); i++)
                result[i] = callback(i);
        });
    });

    arena.execute( [&] { group.wait(); });      
}

void backend_serial(vector<int>& result, std::function<int (int)>& callback)
{
    for (int i = 0; i < result.size(); i++)
        result[i] = callback(i);
}

PYBIND11_MODULE(python_tbb, m)
{
    pybind11::bind_vector<std::vector<int> >(m, "stdvectorint");

    m.def("backend_tbb", &backend_tbb, "TBB backend");
    m.def("backend_serial", &backend_serial, "Serial backend");
}

backend_tbb 未注释,应用程序无限死锁:

from python_tbb import *
import numpy as np

def callback(a) :
    return int(a) * 10

def main() :
    length = 10
    result1 = stdvectorint(np.zeros(length, np.int32))
    result2 = stdvectorint(np.zeros(length, np.int32))

    backend_serial(result1, callback)
        # XXX Uncomment this to get the program hang
    #backend_tbb(result2, callback)

    for i in range(length) :
        print("%d vs %d" % (result1[i], result2[i]))

if __name__ == "__main__" :
    main()

我试过gil_scoped_acquire/gil_scoped_release,但没有任何变化。据报道Similar solution 适用于 OpenMP 循环 - 但当我尝试对 TBB 做同样的事情时,再次没有运气。请对此案提出建议,谢谢!

【问题讨论】:

  • 你可以读到here tbb::blocked_range [i, j) 不保证j&gt;=i。您可以尝试用i &lt; range.end() 替换i != range.end() 吗?也许你在那里运行一个无限循环。即使没有,那也绝对是一个危险。
  • 实际上intel compares the same way you do 所以这不是答案。帮不了你,对不起...
  • @Nearoo 循环应该没问题,适用于单线程情况
  • 当然,在单线程情况下j&gt;i 总是正确的。但由于英特尔也是这样做的,所以这确实不太可能是问题所在。但是,您的论点使我认为您不完全理解代码的作用。你确定这是一个僵局吗?你使用调试器吗?
  • 另外,您的回购实际上只包含您在此处发布的内容。我已将您的问题标记为需要详细信息,因为您显然不明白我的建议是做什么的,并且可能根本不知道您在做什么

标签: python python-3.x tbb pybind11


【解决方案1】:

问题是 TBB 任务在与 task_group 关联的 task_arena 实例中生成,但等待是在另一个名为 arenatask_arena 实例中完成的。这可能导致死锁。要解决此问题,请尝试将对 group.run() 的调用封装到 task_arena.execute() 中,就像对 group.wait() 所做的那样。

但是,在这种情况下,后一种包装似乎是多余的。因此,您可能希望将两个包装合二为一

arena.execute() {
   group.run( /* ... */ );
   group.wait();
}

在这个特定的例子中,这使得task_group 的使用变得不必要,因为主线程产生任务并立即加入以参与它们的执行,就像在tbb::parallel_for 中所做的那样。因此,task_group 只能被删除。

【讨论】:

  • 谢谢,成功了!我想要runwait 的原因是单独的execute 子句是异步的。也就是说,第 35 行的它们之间应该有一些其他代码。
  • 不幸的是,重新打开,因为在某些情况下测试仍然挂起,即使之前的所有问题都已修复。请在 GitHub 上找到更新的代码:github.com/dmikushin/python_tbb
  • @DmitryMikushin,我已经尝试过在线 IDE 中的 C++ 部分。它通过了。请看一下:coliru.stacked-crooked.com/a/ffa51c6611aa46e3 这意味着中间层可能有问题,即 Python 和 C++ 之间。不幸的是,我不是 Python 专家(至少现在)。
  • 当然,TBB 自己工作。是的,只有与 Python 结合使用才有意义。
  • 当我开始了解 TBB 内部时,似乎需要进行以下更改:每次 TBB 线程继续 pause,它应该释放 Python GIL。目前,只要 GIL 没有释放,暂停的 TBB 线程就会阻止其他人做任何事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多