Тупик при сочетании Python с TBB - PullRequest
0 голосов
/ 25 января 2020

Здесь Я хотел бы предложить полный тестовый пример, показывающий простую конструкцию TBB parallel_for, вызывающую тупик в приложении Python. Python внешний интерфейс объединяется с бэкэндом TBB с использованием pybind11:

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, но нет изменение. Подобное решение по сообщениям работает для OpenMP l oop - но опять же не повезло, когда я пытаюсь сделать то же самое для TBB. Пожалуйста, дайте совет по этому делу, спасибо!

1 Ответ

1 голос
/ 26 января 2020

Проблема в том, что задачи TBB появляются в экземпляре task_arena, связанном с task_group, но ожидание выполняется в другом экземпляре task_arena, который называется arena. Это может привести к тупику. Чтобы решить эту проблему, попробуйте заключить вызов в group.run() в task_arena.execute() аналогично тому, как это делается для group.wait().

Однако в этом случае последняя упаковка представляется излишней. Таким образом, вы можете захотеть объединить две обертки в одну

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

, что в данном конкретном примере делает использование task_group ненужным, поскольку главный поток порождает задачи и сразу присоединяется для участия в их выполнении. так же, как это сделано в tbb::parallel_for. Таким образом, task_group может быть просто удален.

...