- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
[cpp]tbb::parallel_invoke(func_1, func_2, ..., func_n);[/cpp]
[cpp]tbb::begin_parallel_invoke(completion_handler, func_1, func_2, ..., func_n);[/cpp]
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I'm not sure that automatically blocking if only a single thread is available would be a valid solution.
TBB's scheduling algorithmassumes that tasks do not block for any significant fraction of their execution time, with direct use of threads being the recommended workaround.
(Added) Question (the reference isn't entirely clear): are enqueued tasks executed after a finite delay even if all the worker threads are kept busy with local work?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
(Added) Under "guarantees asynchronous execution", I mean that the enqueued tasksare executed by TBB worker thread(s) and so do not require the main thread to call a waiting function. However I do not mean any guarantees of making progress on enqueued tasks - as I noted below answering Raf, if worker threads are busy with other work (e.g. TBB algorithms) then enqueued tasks are unattended. Also, when an enqueued background task is executed, the worker thread won't switch to other tasks until done, as the task scheduler is still non-preemptive.
If you want more strict guarantees for simultaneous progress on both background and foreground work, use a separate thread for background work(e.g. std::thread in TBB 3.0) - the OS scheduler is preemptive and more fair, so to say.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
No, tasks in local pools take priority over enqueued tasks.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Different threading tools do handle this situation. In QuickThread
qtControl Boogaloo;
void YourFunction(...
{
... // somewhere in your code
parallel_task(&Boogaloo, func_1);
...
parallel_task(&Boogaloo, func_n);
parallel_task(OnDone$, &Boogaloo, completion_handler);
... // continue executing here
return; // permitted before completion of Boogaloo
}
Although you can use Lambda functions in QuickThread, to do so in this situation could lead to the problem of the Lambda closure objects being created on the stack and then dissapearing during function execution.
You could get away with using the Lambdas provided you did not exit the scope of the invocation prior to completion (same thing with TBB)
void YourFunction(...
{
...
{ // scope
qtControl Boogaloo; // place control inside scope
// run a non-blocking task to perform the blocking invoke
parallel_task(&Boobaloo,
[&]{parallel_invoke(func_1, func_2, ..., func_n);});
// while above is running, post a completon routine
parallel_task(OnDone$, &Boobaloo, completion_handler);
// inside scope
while(YourMainLoopNotDone())
{
... code
}
// cleanup
} // end scope for qtControl Boogaloo; (blocks until all tasks complete)
The above would preserve the Lambda closure objects for the duration of the invocaton
(Note, the func_1, ... can be lambda as well).
Jim Dempsey
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
But you suggestion is absolutely correct for the cases when a lambda is passed to the parallel entity that is not (or may not be) waited for in the current function. For example tbb::task_group copies lambda arguments of its run() methods.
The only other precaution is to capture local variables by value not by reference, but this is definitely pogrammer's responsibility :)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I think the thing you ask for is called std::thread :)
I.e. you start a thread that starts the blocking algorithm - invoke, for, reduce, scan, you name it. It blocks, but the main thread remains responsive. After the parallel algorithm completes, the thread calls any notification method you wish, and exits.
(added) And actually, you might do the same with task::enqueue. I.e. instead of creating a thread you create a task, and enqueue it. The task does the same what I described above.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
[cpp]templateclass async_invoker : public tbb::task { public: async_invoker(Callback callback) : callback_(*new(allocate_continuation()) tbb::internal::function_invoker (callback)) { } tbb::task* execute() { return NULL; } void add_child(const Function& func) { children_.push_back(new(allocate_child()) tbb::internal::function_invoker (func)); this->set_ref_count(children_.size()); std::list<:INTERNAL::FUNCTION_INVOKER> *>::const_iterator it = children_.begin(); std::list<:INTERNAL::FUNCTION_INVOKER> *>::const_iterator itEnd = children_.end(); for (; it != itEnd; ++it) { tbb::internal::function_invoker * child = *it; this->enqueue(*child); } } private: tbb::internal::function_invoker & callback_; std::list<:INTERNAL::FUNCTION_INVOKER> *> children_; }; template void async_invoke(const Callback& callback, const Function& f1, const Function& f2, const Function& f3) { async_invoker & invoker = *new(tbb::task::allocate_root()) async_invoker (callback); invoker.add_child(f1); invoker.add_child(f2); invoker.add_child(f3); tbb::task::spawn(invoker); } [/cpp]
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
[bash] this->set_ref_count(children_.size()); std::list<:INTERNAL::FUNCTION_INVOKER>*>::const_iterator it = children_.begin(); std::list<:INTERNAL::FUNCTION_INVOKER> *>::const_iterator itEnd = children_.end(); for (; it != itEnd; ++it) { tbb::internal::function_invoker * child = *it; this->enqueue(*child); } [/bash]
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Furthermore, invoker should not be spawned, because that is supposed to happen implicitly after the last child finishes executing.
I have not studied the code beyond spotting these obvious problems, but let's see what happens with them first.
Documentation maintenance (for TBB team): the reference currently only mentions "should not spawn" in the "Important" note.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
[cpp]templateclass async_invoker : public tbb::task { public: async_invoker(Callback callback) : callback_(*new(allocate_continuation()) tbb::internal::function_invoker (callback)) { } tbb::task* execute() { this->set_ref_count(children_.size()); std::list<:INTERNAL::FUNCTION_INVOKER> *>::const_iterator it = children_.begin(); std::list<:INTERNAL::FUNCTION_INVOKER> *>::const_iterator itEnd = children_.end(); for (; it != itEnd; ++it) { tbb::internal::function_invoker * child = *it; this->enqueue(*child); } return NULL; } void add_child(const Function& func) { children_.push_back(new(allocate_child()) tbb::internal::function_invoker (func)); } private: tbb::internal::function_invoker & callback_; std::list<:INTERNAL::FUNCTION_INVOKER> *> children_; }; template void async_invoke(const Callback& callback, const Function& f1, const Function& f2, const Function& f3) { async_invoker & invoker = *new(tbb::task::allocate_root()) async_invoker (callback); invoker.add_child(f1); invoker.add_child(f2); invoker.add_child(f3); tbb::task::spawn(invoker); }[/cpp]
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
(Also, prefer std::vector to std::list unless you have a valid reason to use the latter.)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
While you canconstruct aLambda call using [=] (by value), and thus use the value at the time of call as opposed to time of execution, the closure object (the thing containing the values) is a stack object. The current parallel_invoke (both TBB and QuickThread) are blocking, thus are safe from destruction of closure object prior to end of invoked function.
The OP wanted a suggestion for how to have a non-blocking function similar to parallel_invoke. I merely layed out a possible solution and listed the caveats about use of Lambda. In QuickThread you can use a string of parallel_tasks (in addition to or in place of parallel_invoke). parallel_task is non-blocking (although it has slighty higher overhead than parallel_invoke).
An alternate means to reduce overhead, yet maintain non-blocking is: when the task list is large, then an allocated (or static)vector of functors can be walked using a non-blocking parallel_for or parallel_for_each or parallel_distribute or parallel_list.
An additional implementation issue for the OP is if/when these desired functions and completion routine are I/O type of functions then they are not suitable candidates for TBB threads. Oversubscripton of threads would help, up until these threads completed their tasks (at which point theh TBB would run less efficiently).
In QuickThread, you have two classes of threads. The user can select the class of thread in the parallel task. When the task has I/O statements, simply add a flag
parallel_task( IO$, func_1, arg, arg2, ..., argn);
parallel_task( IO$, func_2, arg, arg2, ..., argn);
...
parallel_task( IO$, func_n, arg, arg2, ..., argn);
parallel_task( IO$+OnDone$, CompletionRoutine);
or use lambda's if you want. But be careful of destruction of Lambda closure object prior to task completion.
You generally would add a control object to the parallel_task if/when the enqueued tasks are to have a lifetime longer than the task performing the enqueu operations. (you can also monitor the progress using the control object).
Jim Dempsey

- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page