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

assert failed - thread has not activated a task_scheduler_init object

elhefe38
Beginner
594 Views
Hello

I got the assertion failure mentioned in the title when calling parallel_for from a separate thread. The parallel_for loop is inside a dll that initializes a task_scheduler_init object and a .net application calls processing routines from this dll using .net thread pool.
Do I need to initialize a task_scheduler_init object for every thread I create ? If someone can enlighten me on this topic, I would appreciate :)

Best regards
0 Kudos
10 Replies
RafSchietekat
Valued Contributor III
594 Views
"Do I need to initialize a task_scheduler_init object for every thread I create ?" Yes (to use something like parallel_for, anyway).
0 Kudos
elhefe38
Beginner
594 Views
Quoting - Raf Schietekat
"Do I need to initialize a task_scheduler_init object for every thread I create ?" Yes (to use something like parallel_for, anyway).

Ouch. So I guess my .net thread pool calling a routine containing a parallel_for inside the dll is no longer a valid model...
Time to rewrite some code :P

Thanks for your answer.
0 Kudos
RafSchietekat
Valued Contributor III
594 Views

You don't have to declare the number of participating threads in advance, if that is what you mean, but each thread that involves the scheduler must have a task_scheduler_init, just to register itself.

(Added) For clarity, you can have any number of non-TBB threads (no task_scheduler_init required), any number of TBB threads (task_scheduler_init required to call, e.g., parallel_for), and then there are some worker threads that TBB creates for you to exploit any available parallelism (the parallelism that TBB thinks there is, unless you told it otherwise through the first task_scheduler_init). Only the first task_scheduler_init creates a scheduler, the others just register the thread (if it is not already registered). So far, you don't need to register a thread to be able to use atomics and things like concurrent_hash_map, but that's your responsibility (who knows, maybe sometime some data type will do some background maintenance).

0 Kudos
elhefe38
Beginner
594 Views
Quoting - Raf Schietekat

You don't have to declare the number of participating threads in advance, if that is what you mean, but each thread that involves the scheduler must have a task_scheduler_init, just to register itself.

(Added) For clarity, you can have any number of non-TBB threads (no task_scheduler_init required), any number of TBB threads (task_scheduler_init required to call, e.g., parallel_for), and then there are some worker threads that TBB creates for you to exploit any available parallelism (the parallelism that TBB thinks there is, unless you told it otherwise through the first task_scheduler_init). Only the first task_scheduler_init creates a scheduler, the others just register the thread (if it is not already registered). So far, you don't need to register a thread to be able to use atomics and things like concurrent_hash_map, but that's your responsibility (who knows, maybe sometime some data type will do some background maintenance).


Thanks for the details. This turns to be a little tricky for my current solution, as member functions (using parallel_for) of the same object can be called from UI thread or by the worker (processing frames from a camera).
I guess I have to store the id of threads that call this function and create a task_scheduler_init object each time a new thread comes (there should not be too many of those)...

regards

0 Kudos
RafSchietekat
Valued Contributor III
594 Views

Have you measured the performance impact of creating a task_scheduler_init object on the stack just before calling parallel_for()? You might be pleasantly surprised. Then tell us. :-)

(Added)I'm assuming that there is also along-livedinitialtask_scheduler_init instance somewhere else.

0 Kudos
elhefe38
Beginner
594 Views
Quoting - Raf Schietekat

Have you measured the performance impact of creating a task_scheduler_init object on the stack just before calling parallel_for()? You might be pleasantly surprised. Then tell us. :-)

(Added)I'm assuming that there is also along-livedinitialtask_scheduler_init instance somewhere else.


aha :)
Looks like we are dealing with sub-microsecond overhead so I guess this is the way to go !
Thanks for the tip !
0 Kudos
robert-reed
Valued Contributor II
594 Views
Quoting - elhefe38
aha :)
Looks like we are dealing with sub-microsecond overhead so I guess this is the way to go !
Thanks for the tip !
It sure sounds like you've got at least one thread holding a task_scheduler_init object that persists over the essential lifetime of your application. If you were losing all such objects and recreating them with each romp through the DLL, your overheads would probably be bigger than that, because each time the first such object is created, a pool of worker threads is also spawned, and they go away when the last task_scheduler_init object is destroyed.Reference counting within the management of these objects keeps track of the number instantiated and also keeps the overhead (fairly) low.
0 Kudos
elhefe38
Beginner
594 Views
It sure sounds like you've got at least one thread holding a task_scheduler_init object that persists over the essential lifetime of your application. If you were losing all such objects and recreating them with each romp through the DLL, your overheads would probably be bigger than that, because each time the first such object is created, a pool of worker threads is also spawned, and they go away when the last task_scheduler_init object is destroyed.Reference counting within the management of these objects keeps track of the number instantiated and also keeps the overhead (fairly) low.

Yes I created a task_scheduler_init object when the Dll is loaded.
IMO it would be worth mentioning this behavior in the documentation, this is not easily inferred (and turns out to be quite helpful :)

regards
0 Kudos
robert-reed
Valued Contributor II
594 Views
Quoting - elhefe38
Yes I created a task_scheduler_init object when the Dll is loaded.
IMO it would be worth mentioning this behavior in the documentation, this is not easily inferred (and turns out to be quite helpful :)

Well, here's a quote from the TBB reference manual:

Description

A task_scheduler_init is either "active" or "inactive". Each thread that uses a task should have one active task_scheduler_init object that stays active over the duration that the thread uses task objects. A thread may have more than one active task_scheduler_init at any given moment.

The default constructor for a task_scheduler_init activates it, and the destructor uninitializes it. To defer initialization, pass the value task_scheduler_init::deferred to the constructor. Such a task_scheduler_init may be initialized later by calling method initialize. Destruction of an initialized task_scheduler_init implicitly deactivates it. To deactivate it earlier, call method terminate.

An optional parameter to the constructor and method initialize allow you to specify the number of threads to be used for task execution. This parameter is useful for scaling studies during development, but should not be set for production use. The Tutorial document says more about this topic.

To minimize time overhead, it is best to have a thread create a single task_scheduler_init object whose activation spans all uses of the library's task scheduler. A task_scheduler_init is not assignable or copy-constructible.

So it doesn't talk about the reference counting per se, but the last paragraphsuggests to have a task_scheduler_initobject whose lifetime spans the parallel activities for performance reasons.

0 Kudos
elhefe38
Beginner
594 Views

Well, here's a quote from the TBB reference manual:

Description

A task_scheduler_init is either "active" or "inactive". Each thread that uses a task should have one active task_scheduler_init object that stays active over the duration that the thread uses task objects. A thread may have more than one active task_scheduler_init at any given moment.

The default constructor for a task_scheduler_init activates it, and the destructor uninitializes it. To defer initialization, pass the value task_scheduler_init::deferred to the constructor. Such a task_scheduler_init may be initialized later by calling method initialize. Destruction of an initialized task_scheduler_init implicitly deactivates it. To deactivate it earlier, call method terminate.

An optional parameter to the constructor and method initialize allow you to specify the number of threads to be used for task execution. This parameter is useful for scaling studies during development, but should not be set for production use. The Tutorial document says more about this topic.

To minimize time overhead, it is best to have a thread create a single task_scheduler_init object whose activation spans all uses of the library's task scheduler. A task_scheduler_init is not assignable or copy-constructible.

So it doesn't talk about the reference counting per se, but the last paragraphsuggests to have a task_scheduler_initobject whose lifetime spans the parallel activities for performance reasons.


Yep, I know understand the text you quoted. However only after having actually tried the whole thing...

Regards.
0 Kudos
Reply