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

check if task is running

nagy
New Contributor I
819 Views
ive just converted my 3d engine from boost::threads to tbb and the task system... which have greatly improved hte performacne of my application... however i have one issue where i do not want the "main windows message handling" thread to sleep...

currently my main loop looks something like this (very simplified)

int main()
{
 while( /*Runnning*/)
{
        /* Win32 Message Handling */

tbb::task_list taskList;

taskList.push_back(renderer_->CreateTask());
taskList.push_back(ai_->CreateTask());
taskList.push_back(animation_->CreateTask());
taskList.push_back(physics_->CreateTask());

tbb::task::spawn_root_and_wait(taskList);

/* synchronize shared memory */
}
}

basicly when the Message ahndling ahs finished each subsystem creates a "task"
which is then executed...

the issues here is that the loop has to wait for the "taskList" to finish executing...
which i dont want... i would need some way to check if the "taskList" has finished...
and only then respawn...

so i can do this

int main()
{
tbb::task_list taskList;

while( /*Runnning*/)
{
/* Win32 Message Handling */

if(/* tasklist finished */)
{

taskList.push_back(renderer_->CreateTask());
taskList.push_back(ai_->CreateTask());
taskList.push_back(animation_->CreateTask());
taskList.push_back(physics_->CreateTask());

tbb::task::spawn(taskList);

/* synchronize shared memory */
}
}
}

any suggestions?

alternativly i could have each task "respawn itself" once its finished... and id only do the "wait_for_all" when the application stops...
but im unsure how i would do that... i could have the task keep adding children to itself... but then how would i stop the execution?

0 Kudos
13 Replies
Dmitry_Vyukov
Valued Contributor I
819 Views
nagy:

ive just converted my 3d engine from boost::threads to tbb and the task system... which have greatly improved hte performacne of my application... however i have one issue where i do not want the "main windows message handling" thread to sleep...


I think that the 'right way' to do this is to detect when both tasks have completed and send a message to main thread via PostMessage().
How detect when both tasks have completed you can see here:
http://software.intel.com/en-us/forums//topic/60503
Basically, you need some reference counter, which initially initialized to the number of tasks. When task completes it decrements counter. When counter hits zero, you send a message to main thread.
Here is very dirty code sketch:

main_loop()
{
int ref_count;
for (;;)
{
int msg = GetMessage();
if (msg == SPAWN_CHILD_TASKS) {
ref_count = 2;
spawn(new child_task(&ref_count));
spawn(new child_task(&ref_count));
// not waiting for them to complete, just spawn
}
else if (other messages)
....
}
}

void child_task(int* ref_count)
{
// do real work

if (0 == InterlockedDecrement(ref_count))
// it's the last task, notify main thread
PostMessage(main_thread, SPAWN_CHILD_TASKS);
}

0 Kudos
nagy
New Contributor I
819 Views
thx... ive implemented similar to waht uve suggested... instead of using "PostMessage" i use an atomic in each system which indicates whether the system has finished running or not... this will allow me later to detected which systems are running and not...

class System
{
void Run()
{
isRunning_ = true;
/* do stuff */
isRunning_ = false;
}
atomic isRunning_;
}

and my main loop looks like this now

tbb::task* execute()
{
MSG msg = {0};

while (msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
if(!demo_->renderer_->IsRunning() && !demo_->simulation_->IsRunning())
{
demo_->camera_->Update();
demo_->factory_->Synchronize();

tbb::task& a = *new( allocate_child() ) Renderer::Task(demo_->renderer_.get());
tbb::task& b = *new( allocate_child() ) Simulation::Task(demo_->simulation_.get());

set_ref_count(2); // 2 children + wait_for all at end

spawn(a);
spawn(b);
}
}
}

wait_for_all();

demo_->ExitCode(msg.wParam);

return NULL;
}


however i get a debug assertion from tbb that says that a "ref_count" race was detected...?

however i get a debug assertion from tbb that says that "attempt to spawn task that is not in "allocated" state"

what is causing this?
0 Kudos
nagy
New Contributor I
819 Views
another question i have... will the "parent" task(thread) be continously running or will it "time slice" with its children? wouldnt be to good if it took an entire worker thread just to loop the messages constantly...
0 Kudos
Dmitry_Vyukov
Valued Contributor I
819 Views
nagy:
another question i have... will the "parent" task(thread) be continously running or will it "time slice" with its children? wouldnt be to good if it took an entire worker thread just to loop the messages constantly...


My opinion is that is such situation it's better to not use TBB's worker thread to run message loop at all.
I.e. you run message loop on your own thread (main thread or additionally created thread). That thread submits work to TBB. TBB tasks notify main thread by sending messages via PostMessage, or by setting some variables.


0 Kudos
RafSchietekat
Valued Contributor III
819 Views
About 'however i get a debug assertion from tbb that says that "attempt to spawn task that is not in "allocated" state"': once a task is spawned, set_ref_count() should not be called anymore, and allocate_additional_child_of() used instead.

And of course either execute() should never be made to wait, much less to busy-wait (disclaimer: I only briefly glanced at the code), although it is always possible to "oversubscribe" by one if this thread is in fact waiting almost all of the time (use task_scheduler_init and default_num_threads).

0 Kudos
Alexey-Kukanov
Employee
819 Views

nagy,

The following code should work for your message dispatch loop:

tbb::task* execute()
{
MSG msg = {0};
set_ref_count(1);

while (msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
if(!demo_->renderer_->IsRunning() && !demo_->simulation_->IsRunning())
{
demo_->camera_->Update();
demo_->factory_->Synchronize();
tbb::task& a = *new( allocate_additional_child_of(*this) )
Renderer::Task(demo_->renderer_.get());
tbb::task& b = *new( allocate_additional_child_of(*this) )
Simulation::Task(demo_->simulation_.get());
spawn(a);
spawn(b);
}
}
} // message dispatch loop

wait_for_all();
demo_->ExitCode(msg.wParam);
return NULL;
}

To fix your code, I first of all replaced allocate_child with allocate_additional_child_of, which atomically increments the reference counter of the parent. Then, I added another reference with set_ref_count(1) at the very beginning; this reference is required to call wait_for_all later.

The problem with this solution is exactly as you supposed: the thread will spin in the while loop; it will not participate in the processing of spawned tasks, which means concurrency is mandated, i.e. there should be at least one more thread to process the tasks. What's worse, such busy spinning takes resources away from worker threads.

For a GUI application that should also do some extensive computation, the usual solution is to separate computations into another thread and keep GUI responsive in the main thread. To minimize resource consumption, messages are obtained with GetMessage which makes the caller to sleep if there is no messages, thus yielding compute power to the worker thread(s). The notification that is complete can (or even should?) be done by putting a message into the queue. The notification to start the new portion of work can be done via an event.

Now let's extend this design to use TBB. The main thread still should serve GUI and provide the work to other threads. The problem as we see is to get response that the work is completed - the main thread had to poll on some flags (IsRunning in your case), thus it can't sleep waiting for a message anymore.But if the previous solution for communicationwith a single worker thread worked well, why don't keep it? Let's make that worker thread responsible for further sharing of work, and use TBB from there, not from the GUI thread.

That's the main idea (which is the same actually as Dmitry suggested earlier). You can start this working thread either way convenient to you; TBB suggests the class tbb_thread for that purpose. That thread should wait for a signal from the main thread, then spawn tasks, and wait_for_all of them, and repeat. I believe you will figure out the rest of details, or possibly already did.

0 Kudos
nagy
New Contributor I
819 Views
thank you for all the great answers... i understand how it could be solved but still need a little help


ive got a main thread (window creation, message handling)... it listens if it receives a SPAWN_CHILD_TASKS message it spawns the task otherwise does normal message handling..

ive implemented this using GetMessage, PostThreadMessage and atomic reference counting...

now my problem here is how do i create tbb "child" tasks in a thread that is not a tbb "root" task...

// Main thread loop
if(msg.message == SPAWN_CHILD_TASKS )
{
/* spawn tbb tasks ??? */
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

the only way to spawn a tbb task here is to use "spawn_root_and_wait" which is not what i want... it shouldnt "wait"... and i cant put this loop in a "root task" since "GetMessage" must be in the same thread as the window was created...

EDIT:

"That's the main idea (which is the same actually as Dmitry suggested earlier). You can start this working thread either way convenient to you; TBB suggests the class tbb_thread for that purpose. That thread should wait for a signal from the main thread, then spawn tasks, and wait_for_all of them, and repeat. I believe you will figure out the rest of details, or possibly already did."

i missed this part....

i got it now... thx...
0 Kudos
Alexey-Kukanov
Employee
819 Views

Well, if you still want the main thread to be involved in task spawning... Here is what you could do:

  • before entering the message dispatch loop, create tbb::task_scheduler_init to start worker threads; then allocate tbb::empty_task as a root, but do not spawn it, only set the reference count:
    tbb::task& e = *new (tbb::task::allocate_root()) tbb::empty_task;
    e.set_ref_count(1);
  • inside the loop, use this fake root task to allocate and spawn the child tasks, following the rules I outlined in my previous post:
    e.spawn( *new (e.allocate_additional_child_of(e)) MyTaskClass());
  • after the loop, call wait_for_all on the root task to wait for remaining children, and after it returns, call the method destroy for the roottask passing itself as the parameter:
    e.wait_for_all();
    e.destroy(e);

(Edit) But to me, the solution with a separate thread controlling all the TBB stuff is preferrable. And I just saw your edit above :)

0 Kudos
nagy
New Contributor I
819 Views
well now ive implemented this new way... and i ran intel thread profiler on it... and it looks good apart from that i get long "no thread active" parts!

im getting 0.05s long spikes where no thread is active... the main thread is sleeping and the two tbb worker threads are "spinning"...

this didnt happen when i simply had the main thread spawning tasks (the reason this was bad was because the "main thread had to wait for the tasks to finish")...

i know its hard to tell just like that... but does someone have an idea might be causing this?
0 Kudos
Dmitry_Vyukov
Valued Contributor I
819 Views
nagy:

im getting 0.05s long spikes where no thread is active... the main thread is sleeping and the two tbb worker threads are "spinning"...

i know its hard to tell just like that... but does someone have an idea might be causing this?


There is a subtle bug... okay, not bug, feature:
http://software.intel.com/en-us/forums//topic/58670
which can cause dormancy... because you don't have enough 'parallel slack' :)
But I don't think that it can be steadily reproducible...

0 Kudos
nagy
New Contributor I
819 Views
that might be it... dont know... i have to do some debugging... as far as i can remember all ive done since it worked fine was that i put the task spawning in a seperate thread... im getting 20% of cpu time as no thread active... thats not very good...

ill be back
0 Kudos
nagy
New Contributor I
819 Views
solved it...

i dont know what caused it... but after a full rebuild of all my source it seems to work well..

1% no active thread
97% fully utilized (with no unnecessary processing)

its actually almost precisily twice as fast as when i was running single threaded and about 40% faster than when i was using boost::threads... i take that as a success...

i thank you all for the help... and Intel for such an awesome library

0 Kudos
Alexey-Kukanov
Employee
819 Views

I'm glad you now have the design that satisfies your needs. And thank you for the good words about TBB - it is always good to know that your work helped someone else :)

We were once asked about a list of projects or products that use TBB. If you (or your company) don't mind this kind of publicity, you might leave some words and/or a link to your project in that thread: http://software.intel.com/en-us/forums//topic/59636. Thanks!

0 Kudos
Reply