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

curious behavior of task_scheduler_init

dulantha_f
Beginner
1,299 Views
I have noticed that if I put the call to task_scheduler_init between curly braces the call has no effect. To demonstrate here's my code:

#include "stdafx.h"
#include
#include
#include
#include
#include
#include
#include "tbb/compat/thread"
using namespace System;
//Task class:-
class TaskClass : public tbb::task
{
public:
TaskClass(){};
~TaskClass(){};
tbb::task* execute()
{
std::stringstream ss; // to keep the output in order
ss<<"Thread id: "<<:THIS_THREAD::GET_ID>
std::cout< return NULL;
}
};
int main(array<:STRING> ^args)
{
{ // these make a difference
tbb::task_scheduler_init init(6);
}
TaskClass& task1 = *new(tbb::task::allocate_root()) TaskClass();
tbb::task::enqueue(task1,tbb::priority_normal);
TaskClass& task2 = *new(tbb::task::allocate_root()) TaskClass();
tbb::task::enqueue(task2,tbb::priority_normal);
TaskClass& task3 = *new(tbb::task::allocate_root()) TaskClass();
tbb::task::enqueue(task3,tbb::priority_normal);
TaskClass& task4 = *new(tbb::task::allocate_root()) TaskClass();
tbb::task::enqueue(task4,tbb::priority_normal);
TaskClass& task5 = *new(tbb::task::allocate_root()) TaskClass();
tbb::task::enqueue(task5,tbb::priority_normal);
TaskClass& task6 = *new(tbb::task::allocate_root()) TaskClass();
tbb::task::enqueue(task6,tbb::priority_normal);
std::string temp;
std::cin>>temp; // letting other threads execute
return 0;
}
With braces the output:
Thread id: 6640
Thread id: 6640
Thread id: 6640
Thread id: 6640
Thread id: 6640
Thread id: 6640
Without braces the output:
Thread id: 6884
Thread id: 6200
Thread id: 7020
Thread id: 5984
Thread id: 6420
Thread id: 6884
From documentation I understand that with 3.x each thread from application gets its own pool. But in the above example it is the same thread but seems like with curly braces the pool doesn't get created with the set number.
I'm using TBB version 4.0 update 1.
Is this the intended behavior or a bug?
Thank you.
0 Kudos
17 Replies
SergeyKostrov
Valued Contributor II
1,299 Views
>>...
>>Is this the intended behavior or a bug?

As soon as a local scope is left a destructor of the'task_scheduler_init' classwill be called and it will destroy
the scheduler.

...
int main( array<:STRING> ^args )
{
{ // these make a difference // <=local scope START
tbb::task_scheduler_init init(6);
} // <=local scope END
...

Best regards,
Sergey
0 Kudos
RafSchietekat
Valued Contributor III
1,299 Views
"As soon as a local scope is left a destructor of the'task_scheduler_init' classwill be called and it will destroy the scheduler."
Please note that task_scheduler_init is merely a reference to the scheduler, so the scheduler is created when the first reference appears and only destroyed when the last reference in the entire program goes away. A reference can also be implicit, if the thread does anything TBB-related without an existing explicit task_scheduler_init. But other than that the explanation is correct.
0 Kudos
dulantha_f
Beginner
1,299 Views
So if I need an if statement for example,
if (poolSize>0)
task_scheduler_init init(poolSize);
won't cut it. I need to use a pointer and do a new on task_schedule_init? Is this a correct approach?
0 Kudos
Terry_W_Intel
Employee
1,299 Views
You could create the task_scheduler_init object before the if, using the "deferred" initialization option. Then you can call "initialize" with poolSize inside the if. See 12.2.1 in the reference about deferred, and 12.2.3 for initialize.
0 Kudos
RafSchietekat
Valued Contributor III
1,299 Views
No, a task_scheduler_init instance has an internal reference to the shared scheduler data structures. If you don't want to rely on implicit scheduler initialisation, you should allocate a task_scheduler_init instance on the stack, i.e., as an "automatic" varable, so that its lifetime encompasses the thread's use of TBB facilities (make sure that there's a long-lasting instance in the main thread to avoid overhead from tear-down and recreation of the scheduler). I wouldn't try to allocate it on the heap, even if it does work. The code shown, if it compiles at all (maybe it needs a block?), will allocate a task_scheduler_init instance and immediately destroy it, so it doesn't do anything other than waste some time.
0 Kudos
dulantha_f
Beginner
1,299 Views
Let me give some background to my setup. I need a separate class to do TBB tasks, cannot be done in main.
class TBBTasks
{
public:
TBBTasks(){}
~TBBTasks(){ delete _init; }
setThreadPoolSize(int size)
{
// following lines are what I have now and so far seems to work
_poolSize=size;
if (_poolSize>0)
_init = new tbb::task_scheduler_init((SCAInt32)_poolSize);
}
//users use this method to queue tasks to TBB scheduler
void queue(TaskClass class) // This is a dummy task class. I have a separate class that inherits from
//tbb::task.
{
tbb::task::enqueue([instance of my separate class],priority);
}
private:
int _poolSize;
tbb::task_scheduler_init* _init;// current setup though maybe unappropriate
};
So using a similar (any changes to the methods are welcome) class how can I initialize the scheduler with a set thread pool size so that whenever users queue tasks the scheduler runs with the same number of threads?
0 Kudos
RafSchietekat
Valued Contributor III
1,299 Views
Allocation on the heap will probably work, but it requires more care, as illustrated by the missing initialisation of _init, and by what can go wrong if setThreadPoolSize() is called multiple times, all assuming that the task_scheduler_init instance is created before any other TBB activity happens in the thread and that the instance is only accessed from one thread. You can also allocate a mutex' scoped_lock on the heap, but that similarly defeats the design. Also, you should watch your spelling of variable names. :-)

(Edited 2012-03-19) Funny typo apparently corrected.
0 Kudos
Alexey-Kukanov
Employee
1,299 Views
Creating the instance of task_scheduler_init with deferred initialization, and calling initialize() to it when appropriate, as Terry suggested, should work fine for this case.
0 Kudos
dulantha_f
Beginner
1,299 Views
But the problem is my queue() method is called at different various times. So the scheduler will have to be initialized in the queue method everytime then. And it will be brought down as soon as the queue() method is done. So the scheduler is initialized and destructed over and over. Isn't this a major performance hit?
0 Kudos
Alexey-Kukanov
Employee
1,299 Views
All I meant is that in your code dynamic allocation of task_scheduler_init can be replaced with deferred initialization of a class member of this type.

Lazy initialization schemas are also possible, with the overhead as big as checking a flag in each invocation of the queue() method. You can find examples at http://stackoverflow.com/questions/9344739/thread-safe-lazy-creation-with-tbb.
0 Kudos
dulantha_f
Beginner
1,299 Views
I made a static variable and initialized it with deffered and then called initialize(size) with the pool size in the setThreadPoolSize() method. So far it seems to be working fine and I'm not doing a new.
static tbb::task_scheduler_init tbb_TaskSched(task_scheduler_init::deferred); // line in the implementation file
tbb_TaskSched.initialize(size); // line in the setThreadPoolSize()
I feel you guys are really discouraging me to do a new. Any particular reasons as to why not do it?
0 Kudos
RafSchietekat
Valued Contributor III
1,299 Views
#10 "But the problem is my queue() method is called at different various times. So the scheduler will have to be initialized in the queue method everytime then. And it will be brought down as soon as the queue() method is done. So the scheduler is initialized and destructed over and over. Isn't this a major performance hit?"
Indeed, but one which you can counter by keeping another thread/task_scheduler_init combination alive for the duration of the program even if it's not doing anything, e.g., by doing that in main() and then launching another thread from there.

#12 "static tbb::task_scheduler_init tbb_TaskSched(task_scheduler_init::deferred); // line in the implementation file
tbb_TaskSched.initialize(size); // line in the setThreadPoolSize()
I feel you guys are really discouraging me to do a new. Any particular reasons as to why not do it?"
I guess we're both just encouraging you to keep it simple if the scope is logically related to a position on the stack anyway, if only to avoid user error. Initially in this forum thread it wasn't even clear that you needed deferred initialisation (I'll now just accept that you do), so I didn't address the issue before, but note that there's no performance difference between having multiple successive task_scheduler_init instances (whether on the stack or on the heap) and multiple successive initialize()/terminate() uses of a single instance, so the advice above still stands.
0 Kudos
Alexey-Kukanov
Employee
1,299 Views
...note that there's no performance difference between having multiple successive task_scheduler_init instances (whether on the stack or on the heap) and multiple successive initialize()/terminate() uses of a single instance...

Updated: I agree. When writing the original reply, I missed that you talk about multiple successive, and not coexisting,instances.
-----
Calling terminate() on a single instance of task_scheduler_init (TSI)will deactivate it and cause thread pool destruction; subsequent initialize() will create a new pool of threads. Essentially, initialize()/terminate() do the same as construction/destruction of an instance, just at different time. And by the way, an extraTSI instance that is created as deferred does not keep a reference to the pool of threads; you need to have an active instance for that.
0 Kudos
dulantha_f
Beginner
1,299 Views
And by the way, an extraTSI instance that is created as deferred does not keep a reference to the pool of threads; you need to have an active instance for that.

So in my case, once I call initialize() and since the variable is static I get a reference to the TSI during the whole run of the application right?

0 Kudos
Alexey-Kukanov
Employee
1,299 Views
That's right, unless you explicitly call terminate() somewhere.
0 Kudos
RafSchietekat
Valued Contributor III
1,299 Views
#14 "Actually, there is difference."
Well, you're describing exactly what I meant, so no... :-)

0 Kudos
Alexey-Kukanov
Employee
1,299 Views

Oh, it seems I missed just one word: you told about multiple successive instances. Then, yes, I agree, no difference :) Sorry for confusion.

0 Kudos
Reply