Processing math: 0%
 deal.II version GIT relicensing-3075-gc235bd4825 2025-04-15 08:10:00+00:00
\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}} \newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=} \newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]} \newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
thread_management.h
Go to the documentation of this file.
1// ------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: LGPL-2.1-or-later
4// Copyright (C) 2000 - 2024 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Part of the source code is dual licensed under Apache-2.0 WITH
9// LLVM-exception OR LGPL-2.1-or-later. Detailed license information
10// governing the source code and code contributions can be found in
11// LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
12//
13// ------------------------------------------------------------------------
14
15#ifndef dealii_thread_management_h
16#define dealii_thread_management_h
17
18
19#include <deal.II/base/config.h>
20
23#include <deal.II/base/mutex.h>
26
27#ifdef DEAL_II_WITH_TASKFLOW
28# include <taskflow/taskflow.hpp>
29#endif
30
31#include <atomic>
32#include <functional>
33#include <future>
34#include <list>
35#include <memory>
36#include <thread>
37#include <tuple>
38#include <utility>
39#include <vector>
40
41#ifdef DEAL_II_HAVE_CXX20
42# include <concepts>
43#endif
44
45
46#ifdef DEAL_II_WITH_TBB
47# include <tbb/task_group.h>
48#endif
49
51
64namespace Threads
65{
80 template <typename ForwardIterator>
81 std::vector<std::pair<ForwardIterator, ForwardIterator>>
82 split_range(const ForwardIterator &begin,
83 const ForwardIterator &end,
84 const unsigned int n_intervals);
85
94 std::vector<std::pair<unsigned int, unsigned int>>
95 split_interval(const unsigned int begin,
96 const unsigned int end,
97 const unsigned int n_intervals);
98
108 namespace internal
109 {
125 [[noreturn]] void
126 handle_std_exception(const std::exception &exc);
127
135 [[noreturn]] void
137 } // namespace internal
138
143} // namespace Threads
144
145/* ----------- implementation of functions in namespace Threads ---------- */
146#ifndef DOXYGEN
147namespace Threads
148{
149 template <typename ForwardIterator>
150 std::vector<std::pair<ForwardIterator, ForwardIterator>>
151 split_range(const ForwardIterator &begin,
152 const ForwardIterator &end,
153 const unsigned int n_intervals)
154 {
155 using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
156
157 // in non-multithreaded mode, we often have the case that this
158 // function is called with n_intervals==1, so have a shortcut here
159 // to handle that case efficiently
160
161 if (n_intervals == 1)
162 return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
163
164 // if more than one interval requested, do the full work
165 const unsigned int n_elements = std::distance(begin, end);
166 const unsigned int n_elements_per_interval = n_elements / n_intervals;
167 const unsigned int residual = n_elements % n_intervals;
168
169 std::vector<IteratorPair> return_values(n_intervals);
170
171 return_values[0].first = begin;
172 for (unsigned int i = 0; i < n_intervals; ++i)
173 {
174 if (i != n_intervals - 1)
175 {
176 return_values[i].second = return_values[i].first;
177 // note: the cast is performed to avoid a warning of gcc
178 // that in the library `dist>=0' is checked (dist has a
179 // template type, which here is unsigned if no cast is
180 // performed)
181 std::advance(return_values[i].second,
182 static_cast<signed int>(n_elements_per_interval));
183 // distribute residual in division equally among the first
184 // few subintervals
185 if (i < residual)
186 ++return_values[i].second;
187
188 return_values[i + 1].first = return_values[i].second;
189 }
190 else
191 return_values[i].second = end;
192 }
193 return return_values;
194 }
195} // namespace Threads
196
197#endif // DOXYGEN
198
199namespace Threads
200{
201 namespace internal
202 {
221 template <typename RT>
223 {
224 private:
227
228 public:
229 using reference_type = RT &;
230
232 : value()
233 , value_is_initialized(false)
234 {}
235
236 inline reference_type
238 {
239 Assert(
242 "You cannot read the return value of a thread or task "
243 "if that value has not been set. This happens, for example, if "
244 "a task or thread threw an exception."));
245 return value;
246 }
247
248 inline void
249 set(RT &&v)
250 {
251 value = std::move(v);
252 }
253
259 inline void
260 set_from(std::future<RT> &v)
261 {
262 // Get the value from the std::future object. If the future holds
263 // an exception, then the assignment fails, we exit the function via the
264 // exception right away, and value_is_initialized is not set to true --
265 // that's something we can check later on.
266 value = std::move(v.get());
268 }
269 };
270
271
291 template <typename RT>
292 struct return_value<RT &>
293 {
294 private:
295 RT *value;
297
298 public:
299 using reference_type = RT &;
300
302 : value(nullptr)
303 , value_is_initialized(false)
304 {}
305
306 inline reference_type
307 get() const
308 {
309 Assert(
310 value_is_initialized,
312 "You cannot read the return value of a thread or task "
313 "if that value has not been set. This happens, for example, if "
314 "a task or thread threw an exception."));
315 return *value;
316 }
317
318 inline void
319 set(RT &v)
320 {
321 value = &v;
322 }
323
329 inline void
330 set_from(std::future<RT &> &v)
331 {
332 // Get the value from the std::future object. If the future holds
333 // an exception, then the assignment fails, we exit the function via the
334 // exception right away, and value_is_initialized is not set to true --
335 // that's something we can check later on.
336 value = &v.get();
337 value_is_initialized = true;
338 }
339 };
340
341
360 template <>
361 struct return_value<void>
362 {
363 using reference_type = void;
364
365 static inline void
367 {}
368
369
376 inline void
377 set_from(std::future<void> &)
378 {}
379 };
380 } // namespace internal
381
382
383
384 namespace internal
385 {
393 template <typename T>
395 {
396 static T
397 act(const T &t)
398 {
399 return t;
400 }
401 };
402
403
404
412 template <typename T>
413 struct maybe_make_ref<T &>
414 {
415 static std::reference_wrapper<T>
416 act(T &t)
417 {
418 return std::ref(t);
419 }
420 };
421
422
423
430 template <typename RT, typename Function>
432 (std::invocable<Function> &&
433 std::convertible_to<std::invoke_result_t<Function>, RT>))
434 void evaluate_and_set_promise(Function &function, std::promise<RT> &promise)
435 {
436 promise.set_value(function());
437 }
438
439
449 template <typename Function>
450 DEAL_II_CXX20_REQUIRES((std::invocable<Function>))
452 std::promise<void> &promise)
453 {
454 function();
455 promise.set_value();
456 }
457 } // namespace internal
458
459
460
488 template <typename RT = void>
489 class Task
490 {
491 public:
503 Task(const std::function<RT()> &function_object)
504 {
506 {
507#ifdef DEAL_II_WITH_TASKFLOW
508
509 if (MultithreadInfo::get_taskflow_executor().this_worker_id() < 0)
510 {
511 task_data = std::make_shared<TaskData>(
513 function_object));
514 return;
515 }
516#elif defined(DEAL_II_WITH_TBB)
517 // Create a promise object and from it extract a future that
518 // we can use to refer to the outcome of the task. For reasons
519 // explained below, we can't just create a std::promise object,
520 // but have to make do with a pointer to such an object.
521 std::unique_ptr<std::promise<RT>> promise =
522 std::make_unique<std::promise<RT>>();
523 task_data =
524 std::make_shared<TaskData>(std::move(promise->get_future()));
525
526 // Then start the task, using a task_group object (for just this one
527 // task) that is associated with the TaskData object. Note that we
528 // have to *copy* the function object being executed so that it is
529 // guaranteed to live on the called thread as well -- the copying is
530 // facilitated by capturing the 'function_object' variable by value.
531 //
532 // We also have to *move* the promise object into the new task's
533 // memory space because promises can not be copied and we can't refer
534 // to it by reference because it's a local variable of the current
535 // (surrounding) function that may go out of scope before the promise
536 // is ultimately set. This leads to a conundrum: if we had just
537 // declared 'promise' as an object of type std::promise, then we could
538 // capture it in the lambda function via
539 // [..., promise=std::move(promise)]() {...}
540 // and set the promise in the body of the lambda. But setting a
541 // promise is a non-const operation on the promise, and so we would
542 // actually have to declare the lambda function as 'mutable' because
543 // by default, lambda captures are 'const'. That is, we would have
544 // to write
545 // [..., promise=std::move(promise)]() mutable {...}
546 // But this leads to other problems: It turns out that the
547 // tbb::task_group::run() function cannot take mutable lambdas as
548 // argument :-(
549 //
550 // We work around this issue by not declaring the 'promise' variable
551 // as an object of type std::promise, but as a pointer to such an
552 // object. This pointer we can move, and the *pointer* itself can
553 // be 'const' (meaning we can leave the lambda as non-mutable)
554 // even though we modify the object *pointed to*. One would think
555 // that a std::unique_ptr would be the right choice for this, but
556 // that's not true: the resulting lambda function can then be
557 // non-mutable, but the lambda function object is not copyable
558 // and at least some TBB variants require that as well. So
559 // instead we move the std::unique_ptr used above into a
560 // std::shared_ptr to be stored within the lambda function object.
561 task_data->task_group->run(
562 [function_object,
563 promise =
564 std::shared_ptr<std::promise<RT>>(std::move(promise))]() {
565 try
566 {
567 internal::evaluate_and_set_promise(function_object, *promise);
568 }
569 catch (...)
570 {
571 try
572 {
573 // store anything thrown in the promise
574 promise->set_exception(std::current_exception());
575 }
576 catch (...)
577 {
578 // set_exception() may throw too. But ignore this on
579 // the task.
580 }
581 }
582 });
583 return;
584
585#else
586 // If no threading library is supported, just fall back onto C++11
587 // facilities. The problem with this is that the standard does
588 // not actually say what std::async should do. The first
589 // argument to that function can be std::launch::async or
590 // std::launch::deferred, or both. The *intent* of the standard's
591 // authors was probably that if one sets it to
592 // std::launch::async | std::launch::deferred,
593 // that the task is run in a thread pool. But at least as of
594 // 2021, GCC doesn't do that: It just runs it on a new thread.
595 // If one chooses std::launch::deferred, it runs the task on
596 // the same thread but only when one calls join() on the task's
597 // std::future object. In the former case, this leads to
598 // oversubscription, in the latter case to undersubscription of
599 // resources. We choose oversubscription here.
600 //
601 // The issue illustrates why relying on external libraries
602 // with task schedulers is the way to go.
603 task_data = std::make_shared<TaskData>(
604 std::async(std::launch::async | std::launch::deferred,
605 function_object));
606 return;
607#endif
608 }
609 {
610 // Only one thread allowed. So let the task run to completion
611 // and just emplace a 'ready' future.
612 //
613 // The design of std::promise/std::future is unclear, but it
614 // seems that the intent is to obtain the std::future before
615 // we set the std::promise. So create the TaskData object at
616 // the top and then run the task and set the returned
617 // value. Since everything here happens sequentially, it
618 // really doesn't matter in which order all of this is
619 // happening.
620 std::promise<RT> promise;
621 task_data = std::make_shared<TaskData>(promise.get_future());
622 try
623 {
624 internal::evaluate_and_set_promise(function_object, promise);
625 }
626 catch (...)
627 {
628 try
629 {
630 // store anything thrown in the promise
631 promise.set_exception(std::current_exception());
632 }
633 catch (...)
634 {
635 // set_exception() may throw too. But ignore this on
636 // the task.
637 }
638 }
639 }
640 }
641
650 Task() = default;
651
664 Task(const Task &other) = default;
665
679 Task(Task &&other) noexcept = default;
680
694 Task &
695 operator=(const Task &other) = default;
696
711 Task &
712 operator=(Task &&other) noexcept = default;
713
745 void
746 join() const
747 {
748 // Make sure we actually have a task that we can wait for.
750
751 task_data->wait();
752 }
753
766 bool
767 joinable() const
768 {
769 return (task_data != nullptr);
770 }
771
772
824 {
825 // Make sure we actually have a task that we can wait for.
827
828 // Then return the promised object. If necessary, wait for the promise to
829 // be set.
830 return task_data->get();
831 }
832
833
843 "The current object is not associated with a task that "
844 "can be joined. It may have been detached, or you "
845 "may have already joined it in the past.");
847 private:
857 {
858 public:
863 TaskData(std::future<RT> &&future) noexcept
864 : future(std::move(future))
865 , task_has_finished(false)
866#ifdef DEAL_II_WITH_TBB
867 , task_group(std::make_unique<tbb::task_group>())
868#endif
869 {}
870
875 TaskData(const TaskData &) = delete;
876
881 TaskData(TaskData &&) = delete;
882
887 TaskData &
888 operator=(const TaskData &) = delete;
889
894 TaskData &
895 operator=(TaskData &&) = delete;
896
904 ~TaskData() noexcept
905 {
906 // Explicitly wait for the results to be ready. This class stores
907 // a std::future object, and we could just let the compiler generate
908 // the destructor which would then call the destructor of std::future
909 // which *may* block until the future is ready. As explained in
910 // https://en.cppreference.com/w/cpp/thread/future/~future
911 // this is only a *may*, not a *must*. (The standard does not
912 // appear to say anything about it at all.) As a consequence,
913 // let's be explicit about waiting.
914 //
915 // One of the corner cases we have to worry about is that if a task
916 // ends by throwing an exception, then wait() will re-throw that
917 // exception on the thread that calls it, the first time around
918 // someone calls wait() (or the return_value() function of the
919 // surrounding class). So if we get to this constructor and an exception
920 // is thrown by wait(), then that means that the last Task object
921 // referring to a task is going out of scope with nobody having
922 // ever checked the return value of the task itself. In that case,
923 // one could argue that they would also not have cared about whether
924 // an exception is thrown, and that we should simply ignore the
925 // exception. This is what we do here. It is also the simplest solution,
926 // because we don't know what one should do with the exception to begin
927 // with: destructors aren't allowed to throw exceptions, so we can't
928 // just rethrow it here if one had been triggered.
929 try
930 {
931 wait();
932 }
933 catch (...)
934 {}
935 }
936
942 void
944 {
945 // If we have previously already moved the result, then we don't
946 // need a lock and can just return.
948 return;
949
950 // Else, we need to go under a lock and try again. A different thread
951 // may have waited and finished the task since then, so we have to try
952 // a second time. (This is Schmidt's double-checking pattern.)
953 std::lock_guard<std::mutex> lock(mutex);
955 return;
956 else
957 {
958#ifdef DEAL_II_WITH_TASKFLOW
959 // We want to call executor.corun_until() to keep scheduling tasks
960 // until the task we are waiting for has actually finished. The
961 // problem is that TaskFlow documents that you can only call
962 // corun_until() on a worker of the executor. In other words, we
963 // can call it from *inside* other tasks, but not from the main
964 // thread (or other threads that might have been created outside
965 // of TaskFlow).
966 //
967 // Fortunately, we can check whether we are on a worker thread:
968 if (MultithreadInfo::get_taskflow_executor().this_worker_id() >= 0)
969 MultithreadInfo::get_taskflow_executor().corun_until([this]() {
970 return (future.wait_for(std::chrono::seconds(0)) ==
971 std::future_status::ready);
972 });
973 else
974 // We are on a thread not managed by TaskFlow. In that case, we
975 // can simply stop the current thread to wait for the task to
976 // finish (i.e., for the std::future object to become ready). We
977 // can do this because we need not fear that this leads to a
978 // deadlock: The current threads is waiting for completion of a
979 // task that is running on a completely different set of
980 // threads, and so not making any progress here can not deprive
981 // these other threads of the ability to schedule their tasks.
982 //
983 // Indeed, this is even true if the current thread is a worker
984 // of one executor and we are waiting for a task running on a
985 // different executor: The current task being stopped may block
986 // the current executor from scheduling more tasks, but it is
987 // unrelated to the tasks of the scheduler for which we are
988 // waiting for something, and so that other executor will
989 // eventually get around to scheduling the task we are waiting
990 // for, at which point the current task will also complete.
991 future.wait();
992
993#elif defined(DEAL_II_WITH_TBB)
994 // If we build on the TBB, then we can't just wait for the
995 // std::future object to get ready. Apparently the TBB happily
996 // enqueues a task into an arena and then just sits on it without
997 // ever executing it unless someone expresses an interest in the
998 // task. The way to avoid this is to add the task to a
999 // tbb::task_group, and then here wait for the single task
1000 // associated with that task group.
1001 //
1002 // This also makes sense from another perspective. Imagine that
1003 // we allow at most N threads, and that we create N+1 tasks in such
1004 // a way that the first N all wait for the (N+1)st task to finish.
1005 // (See the multithreading/task_17 test for an example.) If they
1006 // all just sit in their std::future::wait() function, nothing
1007 // is ever going to happen because the scheduler sees that N tasks
1008 // are currently running and is never informed that all they're
1009 // doing is wait for another task to finish. What *needs* to
1010 // happen is that the wait() or join() function goes back into
1011 // the scheduler to make sure the scheduler knows that these
1012 // tasks are not actually using CPU time on the thread they're
1013 // working on, and that it is time to run other tasks on the
1014 // same thread -- this is the way we can eventually get that
1015 // (N+1)st task executed, which then unblocks the other N threads.
1016 // (Note that this also implies that multiple tasks can be
1017 // executing on the same thread at the same time -- not
1018 // concurrently, of course, but with one executing and the others
1019 // currently waiting for other tasks to finish.)
1020 //
1021 // If we get here, we know for a fact that atomically
1022 // (because under a lock), no other thread has so far
1023 // determined that we are finished and removed the
1024 // 'task_group' object. So we know that the pointer is
1025 // still valid. But we also know that, because below we
1026 // set the task_has_finished flag to 'true', that no other
1027 // thread will ever get back to this point and query the
1028 // 'task_group' object, so we can delete it.
1029 task_group->wait();
1030 task_group.reset();
1031#endif
1032
1033 // Wait for the task to finish and then move its
1034 // result. (We could have made the set_from() function
1035 // that we call here wait for the future to be ready --
1036 // which happens implicitly when it calls future.get() --
1037 // but that would have required putting an explicit
1038 // future.wait() into the implementation of
1039 // internal::return_value<void>::set_from(), which is a
1040 // bit awkward: that class doesn't actually need to set
1041 // anything, and so it looks odd to have the explicit call
1042 // to future.wait() in the set_from() function. Avoid the
1043 // issue by just explicitly calling future.wait() here.)
1044 future.wait();
1045
1046 // Acquire the returned object. If the task ended in an
1047 // exception, `set_from` will call `std::future::get`, which
1048 // will throw an exception. This leaves `returned_object` in
1049 // an undefined state, but moreover we would bypass setting
1050 // `task_has_finished=true` below. So catch the exception
1051 // for just long enough that we can set that flag, and then
1052 // re-throw it:
1053 try
1054 {
1055 returned_object.set_from(future);
1056 }
1057 catch (...)
1058 {
1059 task_has_finished = true;
1060 throw;
1061 }
1062
1063 // If we got here, the task has ended without an exception and
1064 // we can safely set the flag and return.
1065 task_has_finished = true;
1066 }
1067 }
1068
1069
1070
1073 {
1074 wait();
1075 return returned_object.get();
1076 }
1077
1078 private:
1083 std::mutex mutex;
1084
1089 std::future<RT> future;
1090
1109 std::atomic<bool> task_has_finished;
1110
1116
1117#ifdef DEAL_II_WITH_TBB
1125 std::unique_ptr<tbb::task_group> task_group;
1126
1127 friend class Task<RT>;
1128#endif
1129 };
1130
1135 std::shared_ptr<TaskData> task_data;
1136 };
1137
1138
1139
1159 template <typename RT>
1160 inline Task<RT>
1161 new_task(const std::function<RT()> &function)
1162 {
1163 return Task<RT>(function);
1164 }
1165
1166
1167
1245 template <typename FunctionObjectType>
1246 DEAL_II_CXX20_REQUIRES((std::invocable<FunctionObjectType>))
1247 inline auto new_task(FunctionObjectType function_object)
1248 -> Task<decltype(function_object())>
1249 {
1250 using return_type = decltype(function_object());
1252 return new_task(std::function<return_type()>(function_object));
1253 }
1254
1255
1256
1263 template <typename RT, typename... Args>
1264 inline Task<RT>
1265 new_task(RT (*fun_ptr)(Args...), std_cxx20::type_identity_t<Args>... args)
1266 {
1267 auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1268 return new_task(
1269 [dummy, fun_ptr]() -> RT { return std::apply(fun_ptr, dummy); });
1270 }
1271
1272
1273
1314 template <
1315 typename FunctionObject,
1316 typename... Args,
1317 typename = std::enable_if_t<std::is_invocable_v<FunctionObject, Args...>>,
1318 typename = std::enable_if_t<std::is_function_v<FunctionObject> == false>,
1319 typename =
1320 std::enable_if_t<std::is_member_pointer_v<FunctionObject> == false>,
1321 typename = std::enable_if_t<std::is_pointer_v<FunctionObject> == false>>
1322 inline Task<std::invoke_result_t<FunctionObject, Args...>>
1323 new_task(const FunctionObject &fun, Args &&...args)
1324 {
1325 using RT = std::invoke_result_t<FunctionObject, Args...>;
1326 auto dummy = std::make_tuple(std::forward<Args>(args)...);
1327 return new_task([dummy, fun]() -> RT { return std::apply(fun, dummy); });
1328 }
1329
1330
1331
1338 template <typename RT, typename C, typename... Args>
1339 inline Task<RT>
1340 new_task(RT (C::*fun_ptr)(Args...),
1341 std_cxx20::type_identity_t<C> &c,
1342 std_cxx20::type_identity_t<Args>... args)
1343 {
1344 // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1345 return new_task(std::function<RT()>(std::bind(
1346 fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1347 }
1348
1355 template <typename RT, typename C, typename... Args>
1356 inline Task<RT>
1357 new_task(RT (C::*fun_ptr)(Args...) const,
1358 std_cxx20::type_identity_t<const C> &c,
1359 std_cxx20::type_identity_t<Args>... args)
1360 {
1361 // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1362 return new_task(std::function<RT()>(std::bind(
1363 fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1364 }
1365
1366
1367 // ------------------------ TaskGroup -------------------------------------
1368
1379 template <typename RT = void>
1381 {
1382 public:
1386 TaskGroup &
1388 {
1389 tasks.push_back(t);
1390 return *this;
1391 }
1392
1393
1401 std::size_t
1402 size() const
1403 {
1404 return tasks.size();
1405 }
1406
1421 std::vector<RT>
1423 {
1424 std::vector<RT> results;
1425 results.reserve(size());
1426 for (auto &t : tasks)
1427 results.emplace_back(std::move(t.return_value()));
1428 return results;
1429 }
1430
1431
1438 void
1439 join_all() const
1440 {
1441 for (const auto &t : tasks)
1442 t.join();
1443 }
1444
1445 private:
1449 std::list<Task<RT>> tasks;
1450 };
1451
1452} // namespace Threads
1453
1460#endif
static void initialize_multithreading()
static unsigned int n_threads()
static tf::Executor & get_taskflow_executor()
TaskGroup & operator+=(const Task< RT > &t)
std::vector< RT > return_values()
std::size_t size() const
std::list< Task< RT > > tasks
TaskData(std::future< RT > &&future) noexcept
TaskData(const TaskData &)=delete
TaskData & operator=(const TaskData &)=delete
std::atomic< bool > task_has_finished
internal::return_value< RT > returned_object
std::unique_ptr< tbb::task_group > task_group
TaskData & operator=(TaskData &&)=delete
TaskData(TaskData &&)=delete
internal::return_value< RT >::reference_type get()
Task(Task &&other) noexcept=default
std::shared_ptr< TaskData > task_data
bool joinable() const
internal::return_value< RT >::reference_type return_value()
void join() const
Task & operator=(Task &&other) noexcept=default
Task(const Task &other)=default
Task()=default
Task(const std::function< RT()> &function_object)
Task & operator=(const Task &other)=default
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:35
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:242
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:36
Point< 2 > second
Definition grid_out.cc:4633
static ::ExceptionBase & ExcNoTask()
#define Assert(cond, exc)
#define DeclExceptionMsg(Exception, defaulttext)
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
std::vector< std::pair< ForwardIterator, ForwardIterator > > split_range(const ForwardIterator &begin, const ForwardIterator &end, const unsigned int n_intervals)
Task< RT > new_task(const std::function< RT()> &function)
std::vector< std::pair< unsigned int, unsigned int > > split_interval(const unsigned int begin, const unsigned int end, const unsigned int n_intervals)
void evaluate_and_set_promise(Function &function, std::promise< RT > &promise)
void handle_std_exception(const std::exception &exc)
VectorType::value_type * end(VectorType &V)
VectorType::value_type * begin(VectorType &V)
typename type_identity< T >::type type_identity_t
Definition type_traits.h:95
STL namespace.
static std::reference_wrapper< T > act(T &t)
void set_from(std::future< RT & > &v)
void set_from(std::future< RT > &v)