From: Wolfgang Bangerth Date: Thu, 28 May 2020 20:16:30 +0000 (-0600) Subject: Run operations sequentially in Threads::Task when n_threads==1. X-Git-Tag: v9.3.0-rc1~1515^2 X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f4ff13bda277d93acd12726eb5484a50243a6a66;p=dealii.git Run operations sequentially in Threads::Task when n_threads==1. --- diff --git a/include/deal.II/base/thread_management.h b/include/deal.II/base/thread_management.h index d50787e448..c8f5c4d566 100644 --- a/include/deal.II/base/thread_management.h +++ b/include/deal.II/base/thread_management.h @@ -892,6 +892,36 @@ namespace Threads }; + namespace internal + { + /** + * Set the value of a std::promise object by evaluating the action. + */ + template + void + evaluate_and_set_promise(Function &function, std::promise &promise) + { + promise.set_value(function()); + } + + + /** + * Set the value of a std::promise object by evaluating the + * action. This function is a specialization of the previous one + * for the case where the return type is `void`. Consequently, we + * can't set a value. But we do evaluate the function object and + * call `std::promise::set_value()` without argument. + */ + template + void + evaluate_and_set_promise(Function &function, std::promise &promise) + { + function(); + promise.set_value(); + } + } // namespace internal + + /** * This class describes a task object, i.e., what one obtains by calling @@ -925,16 +955,51 @@ namespace Threads { public: /** - * Construct a task object given a function object to execute on the task, - * and then schedule this function for execution. + * Construct a task object, given a function object to execute on + * the task, and then schedule this function for + * execution. However, when MultithreadInfo::n_threads() returns + * 1, i.e., if the deal.II runtime system has been configured to + * only use one thread, then just execute the given function + * object. * * @post Using this constructor automatically makes the task object * joinable(). */ Task(const std::function &function_object) { - task_data = std::make_shared( - std::move(std::async(std::launch::async, function_object))); + if (MultithreadInfo::n_threads() > 1) + task_data = std::make_shared( + std::async(std::launch::async, function_object)); + else + { + // Only one thread allowed. So let the task run to completion + // and just emplace a 'ready' future. + // + // The design of std::promise/std::future is unclear, but it + // seems that the intent is to obtain the std::future before + // we set the std::promise. So create the TaskData object at + // the top and then run the task and set the returned + // value. Since everything here happens sequentially, it + // really doesn't matter in which order all of this is + // happening. + std::promise promise; + task_data = std::make_shared(promise.get_future()); + try + { + internal::evaluate_and_set_promise(function_object, promise); + } + catch (...) + { + try + { + // store anything thrown in the promise + promise.set_exception(std::current_exception()); + } + catch (...) + {} + // set_exception() may throw too + } + } } /** @@ -1092,11 +1157,20 @@ namespace Threads class TaskData { public: + /** + * Constructor. Initializes an std::future object and assumes + * that the task so set has not finished yet. + */ TaskData(std::future &&future) : future(std::move(future)) , task_has_finished(false) {} + /** + * Wait for the std::future object to be ready, i.e., for the + * time when the std::promise receives its value. If this has + * already happened, this function can follow a fast path. + */ void wait() { @@ -1181,6 +1255,12 @@ namespace Threads * function object without arguments and returning an object of type RT (or * void). * + * @note When MultithreadInfo::n_threads() returns 1, i.e., if the + * deal.II runtime system has been configured to only use one + * thread, then this function just executes the given function + * object immediately and stores the return value in the Task + * object returned by this function. + * * @note Threads::new_task() is, in essence, equivalent to calling * `std::async(std::launch::async, ...)` in that it runs the given task * in the background. (See https://en.cppreference.com/w/cpp/thread/async @@ -1226,6 +1306,12 @@ namespace Threads * can later retrieve via task.return_value() once the * task (i.e., the body of the lambda function) has completed. * + * @note When MultithreadInfo::n_threads() returns 1, i.e., if the + * deal.II runtime system has been configured to only use one + * thread, then this function just executes the given function + * object immediately and stores the return value in the Task + * object returned by this function. + * * @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