Reputation: 9703
I'm running tbb::parallel_for_each
deep within a task_group
. The task_group
is being canceled and that seems to cause tbb::parallel_for_each
to exit without satisfying its postcondition. Here's minimal test case:
tbb::task_group g;
std::vector<int> x { 0, 0, 0, 0 };
std::atomic<std::size_t> counter {0};
g.run([&x, &counter]() {
std::cout << "in run()\n" << std::flush;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "in run(): slept\n" << std::flush;
assert(tbb::task::self().is_cancelled());
tbb::parallel_for_each(x, [&counter](int& y) {
std::cout << "in run(): in parallel_for_each\n" << std::flush;
++y;
++counter;
});
assert(counter == x.size());
});
std::cout << "canceling\n" << std::flush;
g.cancel();
std::cout << "canceled " << g.is_canceling() << " " << tbb::task::self().is_cancelled() << std::endl;
assert(g.is_canceling());
std::cout << "canceled " << g.is_canceling() << " " << tbb::task::self().is_cancelled() << std::endl;
g.wait();
std::cout << "canceled " << g.is_canceling() << " " << tbb::task::self().is_cancelled() << std::endl;
That is: It has tbb::task_group
call run
on a function that waits for 0.1 s, then loops over a vector. It counts how many iterations that loop does. It then cancel()
s the task group. The output is
canceling
in run()
canceled 1 0
canceled 1 0
in run(): slept
Assertion failed: (counter == x.size()), function operator(), file test.cpp
That's to say that the inner loop never happens. My surprise, though, is that tbb::parallel_for_each
is called and exits without exception, but its post-condition (of doing the loop) isn't met!
tbb::task::self().is_cancelled()
after calling tbb::parallel_for_each
if I want to be sure it actually did its job?tbb::parallel_for_each
throw an exception in this case (or at least return bool
)?I can "fix" this by using an isolated context (https://software.intel.com/en-us/node/506075) like this:
tbb::task_group_context root(tbb::task_group_context::isolated);
tbb::parallel_for_each(x, [&counter](int& y) {
std::cout << "in run(): in parallel_for_each\n" << std::flush;
++y;
++counter;
}, root);
but at the moment, I'm not sure when I can ever trust tbb::parallel_for_each
to do its job.
Upvotes: 1
Views: 581
Reputation: 275385
Within a task group, calls to tbb::parallel_for_each
are subordinate to the task group's context.
Cancelling that task group cancels it, and all subordinate task groups. I cannot find where in the docs "subordinate task group" is defined, but it appears to be groups under the group in the "task tree".
A cancelled tbb::parallel_for_each
does not guarantee that all of its iterations are complete. No post condition was violated.
You cancelled the task group, which cancels the subordinate tbb::parallel_for_each
's implicit task group, so it doesn't do the operations.
Your isolated context prevents the cancellation from propagating into the parallel for each's task group. You can replicate the effect in that code by canceling root
prior to calling parallel_for_each
.
Upvotes: 1