Thread Building Blocks: Deadlocks because all threads used up
Asked Answered
A

1

6

In the Intel thread building blocks framework, how does one ensure that all threads are not busy waiting for other threads to complete.

Consider for example the following code,

#include <tbb/tbb.h>
#include <vector>
#include <cstdlib>
#include <future>
#include <iostream>

std::future<bool> run_something(std::function<bool(bool)> func, bool b) {
  auto task = std::make_shared<std::packaged_task<bool()> >(std::bind(func, b));
  std::future<bool> res = task->get_future();
  tbb::task_group g;
  g.run([task]() { (*task)(); });
  return res;
};

int main() {
  tbb::parallel_for(0, 100, 1, [=](size_t i) {
    g.run([] () {
      std::cout << "A" << std::endl;  
      run_something([] (bool b) { return b; }, true).get();
    });
  });
  return EXIT_SUCCESS;
}

Here the main function spawns as tasks as there are threads in the thread pool used by the TBB library. Then when the second call to spawn more tasks happens in the run_something function, the TBB scheduler sees that no threads are available and simply deadlocks. That is I see that that print statement goes through exactly 4 times on a 4 hyper thread machine and 8 times on a 8 hyper thread machine.

How do I avoid this scenario, in particular, is there a way to ensure that two task_group or task_arena or parallel_for constructs use two completely disjoint set of threads?

Afrika answered 24/7, 2015 at 8:22 Comment(2)
Don't run things that depend on one another on the same thread pool. Give each group its own pool.Outplay
But how do I do that. It seems that TBB shares the thread pool by default.Afrika
N
4

The std::future is not compatible with TBB's optional parallelism paradigm. The std::future::get() should really be named as let_me_block_in_system_wait_here(). Unless you want TBB to deadlock, it is prohibited to implement any kind of synchronization between TBB tasks which is not known to TBB task scheduler. That is, express dependencies between your TBB tasks using TBB means.

The optional parallelism means that your code has to be correct running using just a single thread only. Only tbb::task::enqueue() gives you a promise of at least one worker in addition to master thread.

Your code should not even compile since you use g.run() in main() without declaring g. And it is prohibited to destroy task_group before the call to wait() as stated in the reference for the destructor: Requires: Method wait must be called before destroying a task_group, otherwise the destructor throws an exception.

As for the shared thread pool. Yes, TBB has one shared thread pool. But you can control the way how the work is shared using task_arena. No task can leave arena but the worker threads can migrate across arenas in the time between running the tasks.

Nonrestrictive answered 24/7, 2015 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.