From: Daniel Arndt Date: Thu, 31 Aug 2017 22:24:11 +0000 (+0200) Subject: Generalize new_task and new_thread using variadic templates X-Git-Tag: v9.0.0-rc1~1137^2~3 X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=890076902047c538f9fb4b6b0a36339c8395d1fc;p=dealii.git Generalize new_task and new_thread using variadic templates --- diff --git a/include/deal.II/base/thread_management.h b/include/deal.II/base/thread_management.h index 3124845537..cc7c192465 100644 --- a/include/deal.II/base/thread_management.h +++ b/include/deal.II/base/thread_management.h @@ -1823,2344 +1823,701 @@ namespace Threads /** * Overload of the new_thread function for non-member or static member - * functions with no arguments. + * functions. * * @ingroup threads */ - template - inline - Thread - new_thread (RT (*fun_ptr)()) - { - return new_thread (std::function (fun_ptr)); - } - - - /** - * Overload of the non-const new_thread function for member functions with - * no arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(), - typename identity::type &c) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c)))); - } - - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with no - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)() const, - const typename identity::type &c) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c)))); - } -#endif - - - -// ----------- thread starters for unary functions - - /** - * Overload of the new_thread function for non-member or static member - * functions with 1 argument. - * - * @ingroup threads - */ - template + template inline Thread - new_thread (RT (*fun_ptr)(Arg1), - typename identity::type arg1) + new_thread (RT (*fun_ptr)(Args...), + typename identity::type... args) { return new_thread (std::function (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1)))); + internal::maybe_make_ref::act(args)...))); } /** - * Overload of the non-const new_thread function for member functions with 1 - * argument. + * Overload of the non-const new_thread function for member functions. * * @ingroup threads */ - template + template inline Thread - new_thread (RT (C::*fun_ptr)(Arg1), + new_thread (RT (C::*fun_ptr)(Args...), typename identity::type &c, - typename identity::type arg1) + typename identity::type... args) { return new_thread (std::function (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1)))); + internal::maybe_make_ref::act(args)...))); } #ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG /** - * Overload of the new_thread function for const member functions with 1 - * argument. + * Overload of the new_thread function for const member functions. * * @ingroup threads */ - template + template inline Thread - new_thread (RT (C::*fun_ptr)(Arg1) const, + new_thread (RT (C::*fun_ptr)(Args...) const, typename identity::type &c, - typename identity::type arg1) + typename identity::type... args) { return new_thread (std::function (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1)))); + internal::maybe_make_ref::act(args)...))); } #endif -// ----------- thread starters for binary functions +// ------------------------ ThreadGroup ------------------------------------- /** - * Overload of the new_thread function for non-member or static member - * functions with 2 arguments. + * A container for thread objects. Allows to add new thread objects and wait + * for them all together. The thread objects need to have the same return + * value for the called function. * + * @author Wolfgang Bangerth, 2003 * @ingroup threads */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2), - typename identity::type arg1, - typename identity::type arg2) + template + class ThreadGroup { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2)))); - } + public: + /** + * Add another thread object to the collection. + */ + ThreadGroup &operator += (const Thread &t) + { + threads.push_back (t); + return *this; + } + /** + * Wait for all threads in the collection to finish. It is not a problem + * if some of them have already been waited for, i.e. you may call this + * function more than once, and you can also add new thread objects + * between subsequent calls to this function if you want. + */ + void join_all () const + { + for (typename std::list >::const_iterator + t=threads.begin(); t!=threads.end(); ++t) + t->join (); + } + private: + /** + * List of thread objects. + */ + std::list > threads; + }; - /** - * Overload of the non-const new_thread function for member functions with 2 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2)))); - } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 2 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2)))); - } -#endif + template class Task; -// ----------- thread starters for ternary functions - /** - * Overload of the new_thread function for non-member or static member - * functions with 3 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3) + namespace internal { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3)))); - } +#ifdef DEAL_II_WITH_THREADS + template struct TaskDescriptor; + /** + * The task class for TBB that is used by the TaskDescriptor class. + */ + template + struct TaskEntryPoint : public tbb::task + { + TaskEntryPoint (TaskDescriptor &task_descriptor) + : + task_descriptor (task_descriptor) + {} - /** - * Overload of the non-const new_thread function for member functions with 3 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3)))); - } + virtual tbb::task *execute () + { + // call the function object and put the return value into the + // proper place + try + { + call (task_descriptor.function, task_descriptor.ret_val); + } + catch (const std::exception &exc) + { + internal::handle_std_exception (exc); + } + catch (...) + { + internal::handle_unknown_exception (); + } + return nullptr; + } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 3 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3)))); - } -#endif + /** + * A reference to the descriptor object of this task. + */ + TaskDescriptor &task_descriptor; + }; + /** + * @internal + * + * Base class describing a task. This is the basic class abstracting the + * Threading Building Blocks implementation of tasks. It provides a + * mechanism to start a new task, as well as for joining it. + * + * Internally, the way things are implemented is that all Task<> objects + * keep a shared pointer to the task descriptor. When the last Task<> + * object goes out of scope, the destructor of the descriptor is called. + * Since tasks can not be abandoned, the destructor makes sure that the + * task is finished before it can continue to destroy the object. + * + * Note that unlike threads, tasks are not always started right away, and + * so the starting thread can't rely on the fact that the started task can + * copy things off the spawning thread's stack frame. As a consequence, + * the task description needs to include a way to store the function and + * its arguments that shall be run on the task. + * + * @author Wolfgang Bangerth, 2009 + */ + template + struct TaskDescriptor + { + private: + /** + * The function and its arguments that are to be run on the task. + */ + std::function function; -// ----------- thread starters for functions with 4 arguments + /** + * Variable holding the data the TBB needs to work with a task. Set by + * the queue_up_task() function. Note that the object behind this + * pointer will be deleted upon termination of the task, so we do not + * have to do so ourselves. In particular, if all objects with pointers + * to this task_description object go out of scope then no action is + * needed on our behalf. + */ + tbb::task *task; - /** - * Overload of the new_thread function for non-member or static member - * functions with 4 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4) - { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4)))); - } + /** + * A place where the task will deposit its return value. + */ + return_value ret_val; + /** + * A flag indicating whether the task has terminated. + */ + bool task_is_done; + public: - /** - * Overload of the non-const new_thread function for member functions with 4 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4)))); - } + /** + * Constructor. Take the function to be run on this task as argument. + */ + TaskDescriptor (const std::function &function); -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 4 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4)))); - } -#endif + /** + * Default constructor. Throws an exception since we want to queue a + * task immediately upon construction of these objects to make sure that + * each TaskDescriptor object corresponds to exactly one task. + */ + TaskDescriptor (); -// ----------- thread starters for functions with 5 arguments + /** + * Copy constructor. Throws an exception since we want to make sure that + * each TaskDescriptor object corresponds to exactly one task. + */ + TaskDescriptor (const TaskDescriptor &); - /** - * Overload of the new_thread function for non-member or static member - * functions with 5 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5) - { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5)))); - } + /** + * Destructor. + */ + ~TaskDescriptor (); + /** + * Copy operator. Throws an exception since we want to make sure that + * each TaskDescriptor object corresponds to exactly one task. + */ + TaskDescriptor & + operator = (const TaskDescriptor &); + /** + * Queue up the task to the scheduler. We need to do this in a separate + * function since the new tasks needs to access objects from the current + * object and that can only reliably happen if the current object is + * completely constructed already. + */ + void queue_task (); - /** - * Overload of the non-const new_thread function for member functions with 5 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5)))); - } + /** + * Join a task, i.e. wait for it to finish. This function can safely be + * called from different threads at the same time, and can also be + * called more than once. + */ + void join (); -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 5 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5)))); - } -#endif -// ----------- thread starters for functions with 6 arguments + template friend struct TaskEntryPoint; + friend class dealii::Threads::Task; + }; - /** - * Overload of the new_thread function for non-member or static member - * functions with 6 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6) - { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6)))); - } + template + inline + TaskDescriptor::TaskDescriptor (const std::function &function) + : + function (function), + task(nullptr), + task_is_done (false) + {} - /** - * Overload of the non-const new_thread function for member functions with 6 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6)))); - } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 6 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6)))); - } + template + inline + void + TaskDescriptor::queue_task () + { + // use the pattern described in the TBB book on pages 230/231 + // ("Start a large task in parallel with the main program") + task = new (tbb::task::allocate_root()) tbb::empty_task; + task->set_ref_count (2); + + tbb::task *worker = new (task->allocate_child()) TaskEntryPoint(*this); + + // in earlier versions of the TBB, task::spawn was a regular + // member function; however, in later versions, it was converted + // into a static function. we could always call it as a regular member + // function of *task, but that appears to confuse the NVidia nvcc + // compiler. consequently, the following work-around: +#if TBB_VERSION_MAJOR >= 4 + tbb::task::spawn (*worker); +#else + task->spawn (*worker); #endif + } -// ----------- thread starters for functions with 7 arguments - /** - * Overload of the new_thread function for non-member or static member - * functions with 7 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7) - { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7)))); - } + template + TaskDescriptor::TaskDescriptor () + : + task_is_done (false) + { + Assert (false, ExcInternalError()); + } - /** - * Overload of the non-const new_thread function for member functions with 7 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7)))); - } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 7 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7)))); - } -#endif + template + TaskDescriptor::TaskDescriptor (const TaskDescriptor &) + : + task_is_done (false) + { + // we shouldn't be getting here -- task descriptors + // can't be copied + Assert (false, ExcInternalError()); + } -// ----------- thread starters for functions with 8 arguments - /** - * Overload of the new_thread function for non-member or static member - * functions with 8 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8) - { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8)))); - } + template + inline + TaskDescriptor::~TaskDescriptor () + { + // wait for the task to complete for sure + join (); + // now destroy the empty task structure. the book recommends to + // spawn it as well and let the scheduler destroy the object + // when done, but this has the disadvantage that the scheduler + // may not get to actually finishing the task before it goes out + // of scope (at the end of the program, or if a thread is done + // on which it was run) and then we would get a hard-to-decipher + // warning about unfinished tasks when the scheduler "goes out + // of the arena". rather, let's explicitly destroy the empty + // task object. before that, make sure that the task has been + // shut down, expressed by a zero reference count + AssertNothrow (task != nullptr, ExcInternalError()); + AssertNothrow (task->ref_count()==0, ExcInternalError()); + task->destroy (*task); + } - /** - * Overload of the non-const new_thread function for member functions with 8 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8)))); - } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 8 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8)))); - } -#endif - -// ----------- thread starters for functions with 9 arguments - - /** - * Overload of the new_thread function for non-member or static member - * functions with 9 arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8,Arg9), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8, - typename identity::type arg9) - { - return - new_thread (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8), - internal::maybe_make_ref::act(arg9)))); - } - - - - /** - * Overload of the non-const new_thread function for member functions with 9 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8,Arg9), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8, - typename identity::type arg9) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8), - internal::maybe_make_ref::act(arg9)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_thread function for const member functions with 9 - * arguments. - * - * @ingroup threads - */ - template - inline - Thread - new_thread (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8,Arg9) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8, - typename identity::type arg9) - { - return - new_thread (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8), - internal::maybe_make_ref::act(arg9)))); - } -#endif - -// ------------------------ ThreadGroup ------------------------------------- - - /** - * A container for thread objects. Allows to add new thread objects and wait - * for them all together. The thread objects need to have the same return - * value for the called function. - * - * @author Wolfgang Bangerth, 2003 - * @ingroup threads - */ - template - class ThreadGroup - { - public: - /** - * Add another thread object to the collection. - */ - ThreadGroup &operator += (const Thread &t) + template + TaskDescriptor & + TaskDescriptor::operator = (const TaskDescriptor &) { - threads.push_back (t); + // we shouldn't be getting here -- task descriptors + // can't be copied + Assert (false, ExcInternalError()); return *this; } - /** - * Wait for all threads in the collection to finish. It is not a problem - * if some of them have already been waited for, i.e. you may call this - * function more than once, and you can also add new thread objects - * between subsequent calls to this function if you want. - */ - void join_all () const - { - for (typename std::list >::const_iterator - t=threads.begin(); t!=threads.end(); ++t) - t->join (); - } - - private: - /** - * List of thread objects. - */ - std::list > threads; - }; - - - template class Task; - - - namespace internal - { -#ifdef DEAL_II_WITH_THREADS - - template struct TaskDescriptor; - /** - * The task class for TBB that is used by the TaskDescriptor class. - */ template - struct TaskEntryPoint : public tbb::task + inline + void + TaskDescriptor::join () { - TaskEntryPoint (TaskDescriptor &task_descriptor) - : - task_descriptor (task_descriptor) - {} + // if the task is already done, just return. this makes sure we + // call tbb::Task::wait_for_all() exactly once, as required by + // TBB. we could also get the reference count of task for doing + // this, but that is usually slower. note that this does not + // work when the thread calling this function is not the same as + // the one that initialized the task. + // + // TODO: can we assert that no other thread tries to end the + // task? + if (task_is_done == true) + return; - virtual tbb::task *execute () - { - // call the function object and put the return value into the - // proper place - try - { - call (task_descriptor.function, task_descriptor.ret_val); - } - catch (const std::exception &exc) - { - internal::handle_std_exception (exc); - } - catch (...) - { - internal::handle_unknown_exception (); - } - return nullptr; - } + // let TBB wait for the task to complete. + task_is_done = true; + task->wait_for_all(); + } - /** - * A reference to the descriptor object of this task. - */ - TaskDescriptor &task_descriptor; - }; + + +#else // no threading enabled /** - * @internal - * - * Base class describing a task. This is the basic class abstracting the - * Threading Building Blocks implementation of tasks. It provides a - * mechanism to start a new task, as well as for joining it. - * - * Internally, the way things are implemented is that all Task<> objects - * keep a shared pointer to the task descriptor. When the last Task<> - * object goes out of scope, the destructor of the descriptor is called. - * Since tasks can not be abandoned, the destructor makes sure that the - * task is finished before it can continue to destroy the object. - * - * Note that unlike threads, tasks are not always started right away, and - * so the starting thread can't rely on the fact that the started task can - * copy things off the spawning thread's stack frame. As a consequence, - * the task description needs to include a way to store the function and - * its arguments that shall be run on the task. - * - * @author Wolfgang Bangerth, 2009 + * A way to describe tasks. Since we are in non-MT mode at this place, + * things are a lot simpler than in MT mode. */ template struct TaskDescriptor { - private: - /** - * The function and its arguments that are to be run on the task. - */ - std::function function; - - /** - * Variable holding the data the TBB needs to work with a task. Set by - * the queue_up_task() function. Note that the object behind this - * pointer will be deleted upon termination of the task, so we do not - * have to do so ourselves. In particular, if all objects with pointers - * to this task_description object go out of scope then no action is - * needed on our behalf. - */ - tbb::task *task; - /** * A place where the task will deposit its return value. */ return_value ret_val; /** - * A flag indicating whether the task has terminated. - */ - bool task_is_done; - - public: - - /** - * Constructor. Take the function to be run on this task as argument. - */ - TaskDescriptor (const std::function &function); - - /** - * Default constructor. Throws an exception since we want to queue a - * task immediately upon construction of these objects to make sure that - * each TaskDescriptor object corresponds to exactly one task. - */ - TaskDescriptor (); - - /** - * Copy constructor. Throws an exception since we want to make sure that - * each TaskDescriptor object corresponds to exactly one task. - */ - TaskDescriptor (const TaskDescriptor &); - - /** - * Destructor. - */ - ~TaskDescriptor (); - - /** - * Copy operator. Throws an exception since we want to make sure that - * each TaskDescriptor object corresponds to exactly one task. + * Constructor. Call the given function and emplace the return value + * into the slot reserved for this purpose. */ - TaskDescriptor & - operator = (const TaskDescriptor &); + TaskDescriptor (const std::function &function) + { + call (function, ret_val); + } /** - * Queue up the task to the scheduler. We need to do this in a separate - * function since the new tasks needs to access objects from the current - * object and that can only reliably happen if the current object is - * completely constructed already. + * Wait for the task to return. Since we are in non-MT mode here, there + * is nothing to do. */ - void queue_task (); - - /** - * Join a task, i.e. wait for it to finish. This function can safely be - * called from different threads at the same time, and can also be - * called more than once. - */ - void join (); - - - template friend struct TaskEntryPoint; - friend class dealii::Threads::Task; - }; - - - - template - inline - TaskDescriptor::TaskDescriptor (const std::function &function) - : - function (function), - task(nullptr), - task_is_done (false) - {} - - - template - inline - void - TaskDescriptor::queue_task () - { - // use the pattern described in the TBB book on pages 230/231 - // ("Start a large task in parallel with the main program") - task = new (tbb::task::allocate_root()) tbb::empty_task; - task->set_ref_count (2); - - tbb::task *worker = new (task->allocate_child()) TaskEntryPoint(*this); - - // in earlier versions of the TBB, task::spawn was a regular - // member function; however, in later versions, it was converted - // into a static function. we could always call it as a regular member - // function of *task, but that appears to confuse the NVidia nvcc - // compiler. consequently, the following work-around: -#if TBB_VERSION_MAJOR >= 4 - tbb::task::spawn (*worker); -#else - task->spawn (*worker); -#endif - } - - - - template - TaskDescriptor::TaskDescriptor () - : - task_is_done (false) - { - Assert (false, ExcInternalError()); - } - - - - template - TaskDescriptor::TaskDescriptor (const TaskDescriptor &) - : - task_is_done (false) - { - // we shouldn't be getting here -- task descriptors - // can't be copied - Assert (false, ExcInternalError()); - } - - - - template - inline - TaskDescriptor::~TaskDescriptor () - { - // wait for the task to complete for sure - join (); - - // now destroy the empty task structure. the book recommends to - // spawn it as well and let the scheduler destroy the object - // when done, but this has the disadvantage that the scheduler - // may not get to actually finishing the task before it goes out - // of scope (at the end of the program, or if a thread is done - // on which it was run) and then we would get a hard-to-decipher - // warning about unfinished tasks when the scheduler "goes out - // of the arena". rather, let's explicitly destroy the empty - // task object. before that, make sure that the task has been - // shut down, expressed by a zero reference count - AssertNothrow (task != nullptr, ExcInternalError()); - AssertNothrow (task->ref_count()==0, ExcInternalError()); - task->destroy (*task); - } - - - template - TaskDescriptor & - TaskDescriptor::operator = (const TaskDescriptor &) - { - // we shouldn't be getting here -- task descriptors - // can't be copied - Assert (false, ExcInternalError()); - return *this; - } - - - template - inline - void - TaskDescriptor::join () - { - // if the task is already done, just return. this makes sure we - // call tbb::Task::wait_for_all() exactly once, as required by - // TBB. we could also get the reference count of task for doing - // this, but that is usually slower. note that this does not - // work when the thread calling this function is not the same as - // the one that initialized the task. - // - // TODO: can we assert that no other thread tries to end the - // task? - if (task_is_done == true) - return; - - // let TBB wait for the task to complete. - task_is_done = true; - task->wait_for_all(); - } - - - -#else // no threading enabled - - /** - * A way to describe tasks. Since we are in non-MT mode at this place, - * things are a lot simpler than in MT mode. - */ - template - struct TaskDescriptor - { - /** - * A place where the task will deposit its return value. - */ - return_value ret_val; - - /** - * Constructor. Call the given function and emplace the return value - * into the slot reserved for this purpose. - */ - TaskDescriptor (const std::function &function) - { - call (function, ret_val); - } - - /** - * Wait for the task to return. Since we are in non-MT mode here, there - * is nothing to do. - */ - static void join () {} + static void join () {} /** * Run the task. Since we are here in non-MT mode, there is nothing to * do that the constructor hasn't already done. - */ - static void queue_task () {} - }; - -#endif - - } - - - - /** - * Describes one task object based on the Threading Building Blocks' Task. - * Note that the call to join() must be executed on the same thread as the - * call to the constructor. Otherwise, there might be a deadlock. In other - * words, a Task object should never passed on to another task for calling - * the join() method. - * - * @author Wolfgang Bangerth, 2009 - * @ingroup threads - */ - template - class Task - { - public: - /** - * Construct a task object given a function object to execute on the task, - * and then schedule this function for execution. - * - * @post Using this constructor automatically makes the task object - * joinable(). - */ - Task (const std::function &function_object) - { - // create a task descriptor and tell it to queue itself up with - // the scheduling system - task_descriptor.reset (new internal::TaskDescriptor(function_object)); - task_descriptor->queue_task (); - } - - - /** - * Copy constructor. - * - * @post Using this constructor automatically makes the task object - * joinable(). - */ - Task (const Task &t) - : - task_descriptor (t.task_descriptor) - {} - - - /** - * Default constructor. You can't do much with a task object constructed - * this way, except for assigning it a task object that holds data created - * by the Threads::new_task() functions. - * - * @post Using this constructor leaves the object in an unjoinable state, - * i.e., joinable() will return false. - */ - Task () = default; - - /** - * Join the task represented by this object, i.e. wait for it to finish. - * - * A task can be joined multiple times (while the first join() operation - * may block until the task has completed running, all successive attempts - * to join will return immediately). - * - * @pre You can't call this function if you have used the default - * constructor of this class and have not assigned a task object to it. In - * other words, the function joinable() must return true. - */ - void join () const - { - AssertThrow (joinable(), ExcNoTask()); - task_descriptor->join (); - } - - /** - * Return whether the current object can be joined. You can join a task - * object once a task (typically created with Threads::new_task()) has - * actually been assigned to it. On the other hand, the function returns - * false if the object has been default constructed. - * - * A task can be joined multiple times (while the first join() operation - * may block until the task has completed running, all successive attempts - * to join will return immediately). Consequently, if this function - * returns true, it will continue to return true until the task object it - * reports on is assigned to from another object. - */ - bool joinable () const - { - return (task_descriptor != - std::shared_ptr >()); - } - - - /** - * Get the return value of the function of the task. Since this is only - * available once the task finishes, this implicitly also calls join(). - * You can call this function multiple times as long as the object refers - * to the same task, and expect to get the same return value every time. - * - * @pre You can't call this function if you have used the default - * constructor of this class and have not assigned a task object to it. In - * other words, the function joinable() must return true. - */ - RT return_value () - { - join (); - return task_descriptor->ret_val.get(); - } - - - /** - * Check for equality of task objects. Since objects of this class store - * an implicit pointer to an object that exists exactly once for each - * task, the check is simply to compare these pointers. - */ - bool operator == (const Task &t) const - { - AssertThrow (joinable(), ExcNoTask()); - return task_descriptor == t.task_descriptor; - } - - /** - * @addtogroup Exceptions - * @{ - */ - - /** - * Exception - */ - DeclExceptionMsg (ExcNoTask, - "The current object is not associated with a task that " - "can be joined. It may have been detached, or you " - "may have already joined it in the past."); - //@} - private: - /** - * Shared pointer to the object representing the task. Boost's shared - * pointer implementation will make sure that that object lives as long as - * there is at least one subscriber to it. - */ - std::shared_ptr > task_descriptor; - }; - - - - /** - * Overload of the new_task function for objects that can be converted to - * std::function, i.e. anything that can be called like a - * function object without arguments and returning an object of type RT (or - * void). - * - * @ingroup threads - */ - template - inline - Task - new_task (const std::function &function) - { - return Task(function); - } - - - - /** - * Overload of the new_task function for objects that can be called like a - * function object without arguments. In particular, this function allows - * calling Threads::new_task() with either objects that result from using - * std::bind, or using lambda functions. For example, this function is called - * when writing code such as - * @code - * Threads::Task - * task = Threads::new_task ( [] () { - * do_this(); - * then_do_that(); - * return 42; - * }); - * @endcode - * Here, we schedule the call to the sequence of functions - * do_this() and then_do_that() on - * a separate task, by making the lambda function declared here the - * function to execute on the task. The lambda function then returns - * 42 (which is a bit pointless here, but it could of course be some - * computed number), and this is going to be the returned value you - * can later retrieve via task.return_value() once the - * task (i.e., the body of the lambda function) has completed. - * - * @note Every lambda function (or whatever else it is you pass to - * the new_task() function here, for example the result of a - * std::bind() expression) has a return type and consequently - * returns an object of this type. This type can be inferred - * using the C++11 decltype statement used in the - * declaration of this function, and it is then used as the template - * argument of the Threads::Task object returned by the current function. - * In the example above, because the lambda function returns 42 - * (which in C++ has data type int), the inferred - * type is int and the task object will have type - * Task@. In other words, it is not necessary - * to explicitly specify in user code what that return type - * of the lambda or std::bind expression will be, though it is - * possible to explicitly do so by (entirely equivalently) writing - * @code - * Threads::Task - * task = Threads::new_task ( [] () -> int { - * do_this(); - * then_do_that(); - * return 42; - * }); - * @endcode - * - * @note In practice, the lambda functions you will pass to - * new_task() will of course typically be more complicated. - * In particular, they will likely capture variables - * from the surrounding context and use them within the lambda. - * See https://en.wikipedia.org/wiki/Anonymous_function#C.2B.2B_.28since_C.2B.2B11.29 - * for more on how lambda functions work. - * - * @note If you pass a lambda function as an argument to the - * current function that captures a variable by reference, - * or if you use a std::bind that binds a function argument to - * a reference variable using std::ref() or std::cref(), then - * obviously you can only do this if the variables you reference - * or capture have a lifetime that extends at least until the time - * where the task finishes. - * - * @ingroup CPP11 - */ - template - inline - auto - new_task (FunctionObjectType function_object) - -> Task - { - typedef decltype(function_object()) return_type; - return Task(std::function(function_object)); - } - - - - /** - * Overload of the new_task function for non-member or static member - * functions with no arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)()) - { - return new_task (std::function(fun_ptr)); - } - - - /** - * Overload of the non-const new_task function for member functions with no - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(), - typename identity::type &c) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with no - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)() const, - const typename identity::type &c) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c)))); - } -#endif - - - -// ----------- thread starters for unary functions - - /** - * Overload of the new_task function for non-member or static member - * functions with 1 argument. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1), - typename identity::type arg1) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1)))); - } - - - - /** - * Overload of the non-const new_task function for member functions with 1 - * argument. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1), - typename identity::type &c, - typename identity::type arg1) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 1 - * argument. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1) const, - typename identity::type &c, - typename identity::type arg1) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1)))); - } -#endif - -// ----------- thread starters for binary functions - - /** - * Overload of the new_task function for non-member or static member - * functions with 2 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2), - typename identity::type arg1, - typename identity::type arg2) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2)))); - } - - - - /** - * Overload of the non-const new_task function for member functions with 2 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 2 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2)))); - } -#endif - -// ----------- thread starters for ternary functions - - /** - * Overload of the new_task function for non-member or static member - * functions with 3 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3)))); - } - - - - /** - * Overload of the non-const new_task function for member functions with 3 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 3 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3)))); - } -#endif - - -// ----------- thread starters for functions with 4 arguments - - /** - * Overload of the new_task function for non-member or static member - * functions with 4 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4)))); - } - - - - /** - * Overload of the non-const new_task function for member functions with 4 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 4 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4)))); - } -#endif - -// ----------- thread starters for functions with 5 arguments - - /** - * Overload of the new_task function for non-member or static member - * functions with 5 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5)))); - } - - - - /** - * Overload of the non-const new_task function for member functions with 5 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 5 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5)))); - } -#endif - -// ----------- thread starters for functions with 6 arguments - - /** - * Overload of the new_task function for non-member or static member - * functions with 6 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6)))); - } - - - - /** - * Overload of the non-const new_task function for member functions with 6 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6) - { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6)))); - } - -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 6 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6)))); - } -#endif - -// ----------- thread starters for functions with 7 arguments - - /** - * Overload of the new_task function for non-member or static member - * functions with 7 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7)))); + */ + static void queue_task () {} + }; + +#endif + } /** - * Overload of the non-const new_task function for member functions with 7 - * arguments. + * Describes one task object based on the Threading Building Blocks' Task. + * Note that the call to join() must be executed on the same thread as the + * call to the constructor. Otherwise, there might be a deadlock. In other + * words, a Task object should never passed on to another task for calling + * the join() method. * + * @author Wolfgang Bangerth, 2009 * @ingroup threads */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7) + template + class Task { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7)))); - } + public: + /** + * Construct a task object given a function object to execute on the task, + * and then schedule this function for execution. + * + * @post Using this constructor automatically makes the task object + * joinable(). + */ + Task (const std::function &function_object) + { + // create a task descriptor and tell it to queue itself up with + // the scheduling system + task_descriptor.reset (new internal::TaskDescriptor(function_object)); + task_descriptor->queue_task (); + } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG - /** - * Overload of the new_task function for const member functions with 7 - * arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7) - { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7)))); - } -#endif -// ----------- thread starters for functions with 8 arguments + /** + * Copy constructor. + * + * @post Using this constructor automatically makes the task object + * joinable(). + */ + Task (const Task &t) + : + task_descriptor (t.task_descriptor) + {} - /** - * Overload of the new_task function for non-member or static member - * functions with 8 arguments. - * - * @ingroup threads - */ - template - inline - Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8) - { - return - new_task (std::function - (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8)))); - } + + /** + * Default constructor. You can't do much with a task object constructed + * this way, except for assigning it a task object that holds data created + * by the Threads::new_task() functions. + * + * @post Using this constructor leaves the object in an unjoinable state, + * i.e., joinable() will return false. + */ + Task () = default; + + /** + * Join the task represented by this object, i.e. wait for it to finish. + * + * A task can be joined multiple times (while the first join() operation + * may block until the task has completed running, all successive attempts + * to join will return immediately). + * + * @pre You can't call this function if you have used the default + * constructor of this class and have not assigned a task object to it. In + * other words, the function joinable() must return true. + */ + void join () const + { + AssertThrow (joinable(), ExcNoTask()); + task_descriptor->join (); + } + + /** + * Return whether the current object can be joined. You can join a task + * object once a task (typically created with Threads::new_task()) has + * actually been assigned to it. On the other hand, the function returns + * false if the object has been default constructed. + * + * A task can be joined multiple times (while the first join() operation + * may block until the task has completed running, all successive attempts + * to join will return immediately). Consequently, if this function + * returns true, it will continue to return true until the task object it + * reports on is assigned to from another object. + */ + bool joinable () const + { + return (task_descriptor != + std::shared_ptr >()); + } + + + /** + * Get the return value of the function of the task. Since this is only + * available once the task finishes, this implicitly also calls join(). + * You can call this function multiple times as long as the object refers + * to the same task, and expect to get the same return value every time. + * + * @pre You can't call this function if you have used the default + * constructor of this class and have not assigned a task object to it. In + * other words, the function joinable() must return true. + */ + RT return_value () + { + join (); + return task_descriptor->ret_val.get(); + } + + + /** + * Check for equality of task objects. Since objects of this class store + * an implicit pointer to an object that exists exactly once for each + * task, the check is simply to compare these pointers. + */ + bool operator == (const Task &t) const + { + AssertThrow (joinable(), ExcNoTask()); + return task_descriptor == t.task_descriptor; + } + + /** + * @addtogroup Exceptions + * @{ + */ + + /** + * Exception + */ + DeclExceptionMsg (ExcNoTask, + "The current object is not associated with a task that " + "can be joined. It may have been detached, or you " + "may have already joined it in the past."); + //@} + private: + /** + * Shared pointer to the object representing the task. Boost's shared + * pointer implementation will make sure that that object lives as long as + * there is at least one subscriber to it. + */ + std::shared_ptr > task_descriptor; + }; /** - * Overload of the non-const new_task function for member functions with 8 - * arguments. + * Overload of the new_task function for objects that can be converted to + * std::function, i.e. anything that can be called like a + * function object without arguments and returning an object of type RT (or + * void). * * @ingroup threads */ - template + template inline Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8), - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8) + new_task (const std::function &function) { - return - new_task (std::function - (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8)))); + return Task(function); } -#ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG + + /** - * Overload of the new_task function for const member functions with 8 - * arguments. + * Overload of the new_task function for objects that can be called like a + * function object without arguments. In particular, this function allows + * calling Threads::new_task() with either objects that result from using + * std::bind, or using lambda functions. For example, this function is called + * when writing code such as + * @code + * Threads::Task + * task = Threads::new_task ( [] () { + * do_this(); + * then_do_that(); + * return 42; + * }); + * @endcode + * Here, we schedule the call to the sequence of functions + * do_this() and then_do_that() on + * a separate task, by making the lambda function declared here the + * function to execute on the task. The lambda function then returns + * 42 (which is a bit pointless here, but it could of course be some + * computed number), and this is going to be the returned value you + * can later retrieve via task.return_value() once the + * task (i.e., the body of the lambda function) has completed. * - * @ingroup threads + * @note Every lambda function (or whatever else it is you pass to + * the new_task() function here, for example the result of a + * std::bind() expression) has a return type and consequently + * returns an object of this type. This type can be inferred + * using the C++11 decltype statement used in the + * declaration of this function, and it is then used as the template + * argument of the Threads::Task object returned by the current function. + * In the example above, because the lambda function returns 42 + * (which in C++ has data type int), the inferred + * type is int and the task object will have type + * Task@. In other words, it is not necessary + * to explicitly specify in user code what that return type + * of the lambda or std::bind expression will be, though it is + * possible to explicitly do so by (entirely equivalently) writing + * @code + * Threads::Task + * task = Threads::new_task ( [] () -> int { + * do_this(); + * then_do_that(); + * return 42; + * }); + * @endcode + * + * @note In practice, the lambda functions you will pass to + * new_task() will of course typically be more complicated. + * In particular, they will likely capture variables + * from the surrounding context and use them within the lambda. + * See https://en.wikipedia.org/wiki/Anonymous_function#C.2B.2B_.28since_C.2B.2B11.29 + * for more on how lambda functions work. + * + * @note If you pass a lambda function as an argument to the + * current function that captures a variable by reference, + * or if you use a std::bind that binds a function argument to + * a reference variable using std::ref() or std::cref(), then + * obviously you can only do this if the variables you reference + * or capture have a lifetime that extends at least until the time + * where the task finishes. + * + * @ingroup CPP11 */ - template + template inline - Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8) const, - typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8) + auto + new_task (FunctionObjectType function_object) + -> Task { - return - new_task (std::function - (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8)))); + typedef decltype(function_object()) return_type; + return Task(std::function(function_object)); } -#endif -// ----------- thread starters for functions with 9 arguments + /** * Overload of the new_task function for non-member or static member - * functions with 9 arguments. + * functions. * * @ingroup threads */ - template + template inline Task - new_task (RT (*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8,Arg9), - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8, - typename identity::type arg9) + new_task (RT (*fun_ptr)(Args...), + typename identity::type... args) { return new_task (std::function (std::bind(fun_ptr, - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8), - internal::maybe_make_ref::act(arg9)))); + internal::maybe_make_ref::act(args)...))); } /** - * Overload of the non-const new_task function for member functions with 9 - * arguments. + * Overloads of the non-const new_task function. * * @ingroup threads */ - template + template inline Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8,Arg9), + new_task (RT (C::*fun_ptr)(Args...), typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8, - typename identity::type arg9) + typename identity::type... args) { return new_task (std::function (std::bind(fun_ptr, std::ref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8), - internal::maybe_make_ref::act(arg9)))); + internal::maybe_make_ref::act(args)...))); } #ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG /** - * Overload of the new_task function for const member functions with 9 - * arguments. + * Overloads of the new_task function. * * @ingroup threads */ - template + template inline Task - new_task (RT (C::*fun_ptr)(Arg1,Arg2,Arg3,Arg4,Arg5, - Arg6,Arg7,Arg8,Arg9) const, + new_task (RT (C::*fun_ptr)(Args...) const, typename identity::type &c, - typename identity::type arg1, - typename identity::type arg2, - typename identity::type arg3, - typename identity::type arg4, - typename identity::type arg5, - typename identity::type arg6, - typename identity::type arg7, - typename identity::type arg8, - typename identity::type arg9) + typename identity::type... args) { return new_task (std::function (std::bind(fun_ptr, std::cref(c), - internal::maybe_make_ref::act(arg1), - internal::maybe_make_ref::act(arg2), - internal::maybe_make_ref::act(arg3), - internal::maybe_make_ref::act(arg4), - internal::maybe_make_ref::act(arg5), - internal::maybe_make_ref::act(arg6), - internal::maybe_make_ref::act(arg7), - internal::maybe_make_ref::act(arg8), - internal::maybe_make_ref::act(arg9)))); + internal::maybe_make_ref::act(args)...))); } #endif