Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.

What is wrong with this task usage?

Zhu_W_Intel
Employee
450 Views
I have one example: in my caller code, I would like to run a section of code in parallel, so I created a two task which then call a one task twice. Code looks like this:

class TwoTask: public tbb::task {
class TOneTask: public tbb::task {
public:
tbb::task* execute() {
// my implementation
}
OneTask( ...)
};
public:
tbb::task* execute() {
OneTask& a = *new( tbb::task::allocate_child() ) OneTask();
OneTask& b = *new( tbb::task::allocate_child() ) OneTask();
set_ref_count(2);
spawn(a);
return &b;
}
TwoTask () {}
};


// in my caller code
TwoTask &cefft = *new( tbb::task::allocate_root() )TwoTask (,,,);
tbb::task::spawn_root_and_wait(cefft);
cefft.destroy(cefft);


The problem is I always get segment violation when I run with 2 threads. But if I remove the spawn code, replace just the execute() function, so the code run in serial mode, everything is fine. There is no overlap memory write between two onetasks.

What is wrong with this code? Please help.
Thanks.
0 Kudos
4 Replies
RafSchietekat
Valued Contributor III
450 Views
How about:
[cpp]tbb::task* execute() {
set_ref_count(2/*spawn*/+1/*wait_for_all*/);
OneTask& a = *new( tbb::task::allocate_child() ) OneTask();
OneTask& b = *new( tbb::task::allocate_child() ) OneTask();
spawn(a);
spawn_and_wait_for_all(b);
return NULL;
}
[/cpp]
I don't see how destroy() would not cause a problem in the caller code, though, because spawn_root_and_wait() should have destroyed its argument.
0 Kudos
Zhu_W_Intel
Employee
450 Views
Quoting - Raf Schietekat
How about:
[cpp]tbb::task* execute() {
set_ref_count(2/*spawn*/+1/*wait_for_all*/);
OneTask& a = *new( tbb::task::allocate_child() ) OneTask();
OneTask& b = *new( tbb::task::allocate_child() ) OneTask();
spawn(a);
spawn_and_wait_for_all(b);
return NULL;
}
[/cpp]
I don't see how destroy() would not cause a problem in the caller code, though, because spawn_root_and_wait() should have destroyed its argument.

Sorry, I am not sure I get it. Do you mean this is the execute for TwoTask? How about caller code? I should not spawn in the caller code?

Are you saying the "destroy" is not necessary?
0 Kudos
RafSchietekat
Valued Contributor III
450 Views
Yes, this should be TwoTask::execute(), and in the caller code I would not call destroy() (does it really cause no problems?), but the rest remains the same.

(Added) Have a look at "Simple Example: Fibonacci Numbers" in the Tutorial.
0 Kudos
Alexey-Kukanov
Employee
450 Views
Raf is right. A TBB task is by default destroyed after execution, unless the programmer explicitly recycled it. So the original code has two problems: 1) child tasks decrement reference count of a destroyed parent, and 2) explicit call to destroy() at the end is foralready destroyed task. The second one is obvious to fix. The first one should be fixed either with explicit wait for children (i.e. spawn_and_wait_for_all as Raf suggested; note that the reference counter should be 3 in this case) or with recycling the TwoTask as continuation (note that then it will run for a second time after children completion, and take care that this second invocation is a no-op).

In just-released TBB 2.2, more user-friendly ways to run code sections in parallel are available, namely tbb::parallel_invoke function and tbb::task_group class.
0 Kudos
Reply