deal.II version GIT relicensing-6809-ge913b9bb34 2026-09-25 17:20:01+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\}}\)
Loading...
Searching...
No Matches
thread_management.h
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2000 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13#ifndef dealii_thread_management_h
14#define dealii_thread_management_h
15
16
17#include <deal.II/base/config.h>
18
21#include <deal.II/base/mutex.h>
24
25#ifdef DEAL_II_WITH_TASKFLOW
26# include <taskflow/taskflow.hpp>
27#endif
28
29#include <atomic>
30#include <functional>
31#include <future>
32#include <list>
33#include <memory>
34#include <thread>
35#include <tuple>
36#include <utility>
37#include <vector>
38
39#ifdef DEAL_II_HAVE_CXX20
40# include <concepts>
41#endif
42
43
44#ifdef DEAL_II_WITH_TBB
45# include <tbb/task_group.h>
46#endif
47
49
62namespace Threads
63{
78 template <typename ForwardIterator>
79 std::vector<std::pair<ForwardIterator, ForwardIterator>>
80 split_range(const ForwardIterator &begin,
81 const ForwardIterator &end,
82 const unsigned int n_intervals);
83
92 std::vector<std::pair<unsigned int, unsigned int>>
93 split_interval(const unsigned int begin,
94 const unsigned int end,
95 const unsigned int n_intervals);
96
106 namespace internal
107 {
123 [[noreturn]] void
124 handle_std_exception(const std::exception &exc);
125
133 [[noreturn]] void
135 } // namespace internal
136
141} // namespace Threads
142
143/* ----------- implementation of functions in namespace Threads ---------- */
144#ifndef DOXYGEN
145namespace Threads
146{
147 template <typename ForwardIterator>
148 std::vector<std::pair<ForwardIterator, ForwardIterator>>
149 split_range(const ForwardIterator &begin,
150 const ForwardIterator &end,
151 const unsigned int n_intervals)
152 {
153 using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
154
155 // in non-multithreaded mode, we often have the case that this
156 // function is called with n_intervals==1, so have a shortcut here
157 // to handle that case efficiently
158
159 if (n_intervals == 1)
160 return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
161
162 // if more than one interval requested, do the full work
163 const unsigned int n_elements = std::distance(begin, end);
164 const unsigned int n_elements_per_interval = n_elements / n_intervals;
165 const unsigned int residual = n_elements % n_intervals;
166
167 std::vector<IteratorPair> return_values(n_intervals);
168
169 return_values[0].first = begin;
170 for (unsigned int i = 0; i < n_intervals; ++i)
171 {
172 if (i != n_intervals - 1)
173 {
174 return_values[i].second = return_values[i].first;
175 // note: the cast is performed to avoid a warning of gcc
176 // that in the library `dist>=0' is checked (dist has a
177 // template type, which here is unsigned if no cast is
178 // performed)
179 std::advance(return_values[i].second,
180 static_cast<signed int>(n_elements_per_interval));
181 // distribute residual in division equally among the first
182 // few subintervals
183 if (i < residual)
184 ++return_values[i].second;
185
186 return_values[i + 1].first = return_values[i].second;
187 }
188 else
189 return_values[i].second = end;
190 }
191 return return_values;
192 }
193} // namespace Threads
194
195#endif // DOXYGEN
196
197namespace Threads
198{
199 namespace internal
200 {
219 template <typename RT>
221 {
222 private:
225
226 public:
227 using reference_type = RT &;
228
230 : value()
231 , value_is_initialized(false)
232 {}
233
234 inline reference_type
236 {
237 Assert(
240 "You cannot read the return value of a thread or task "
241 "if that value has not been set. This happens, for example, if "
242 "a task or thread threw an exception."));
243 return value;
244 }
245
246 inline void
247 set(RT &&v)
248 {
249 value = std::move(v);
250 }
251
257 inline void
258 set_from(std::future<RT> &v)
259 {
260 // Get the value from the std::future object. If the future holds
261 // an exception, then the assignment fails, we exit the function via the
262 // exception right away, and value_is_initialized is not set to true --
263 // that's something we can check later on.
264 value = std::move(v.get());
266 }
267 };
268
269
289 template <typename RT>
290 struct return_value<RT &>
291 {
292 private:
293 RT *value;
295
296 public:
297 using reference_type = RT &;
298
300 : value(nullptr)
301 , value_is_initialized(false)
302 {}
303
304 inline reference_type
305 get() const
306 {
307 Assert(
308 value_is_initialized,
310 "You cannot read the return value of a thread or task "
311 "if that value has not been set. This happens, for example, if "
312 "a task or thread threw an exception."));
313 return *value;
314 }
315
316 inline void
317 set(RT &v)
318 {
319 value = &v;
320 }
321
327 inline void
328 set_from(std::future<RT &> &v)
329 {
330 // Get the value from the std::future object. If the future holds
331 // an exception, then the assignment fails, we exit the function via the
332 // exception right away, and value_is_initialized is not set to true --
333 // that's something we can check later on.
334 value = &v.get();
335 value_is_initialized = true;
336 }
337 };
338
339
358 template <>
359 struct return_value<void>
360 {
361 using reference_type = void;
362
363 static inline void
365 {}
366
367
374 inline void
375 set_from(std::future<void> &)
376 {}
377 };
378 } // namespace internal
379
380
381
382 namespace internal
383 {
391 template <typename T>
393 {
394 static T
395 act(const T &t)
396 {
397 return t;
398 }
399 };
400
401
402
410 template <typename T>
411 struct maybe_make_ref<T &>
412 {
413 static std::reference_wrapper<T>
414 act(T &t)
415 {
416 return std::ref(t);
417 }
418 };
419
420
421
428 template <typename RT, typename Function>
430 (std::invocable<Function> &&
431 std::convertible_to<std::invoke_result_t<Function>, RT>))
432 void evaluate_and_set_promise(Function &function, std::promise<RT> &promise)
433 {
434 promise.set_value(function());
435 }
436
437
447 template <typename Function>
448 DEAL_II_CXX20_REQUIRES((std::invocable<Function>))
450 std::promise<void> &promise)
451 {
452 function();
453 promise.set_value();
454 }
455 } // namespace internal
456
457
458
486 template <typename RT = void>
487 class Task
488 {
489 public:
501 Task(const std::function<RT()> &function_object)
502 {
504 {
505#ifdef DEAL_II_WITH_TASKFLOW
506 // If we are creating the task from a thread not managed by the
507 // current Taskflow executor, then emplace the new task and run it
508 // asynchronously. Otherwise, we would be asking Taskflow to emplace
509 // a new task with the same executor, which may lead to deadlocks
510 // (see
511 // https://taskflow.github.io/taskflow/ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker
512 // and the discussion in
513 // https://github.com/dealii/dealii/issues/19079). As a consequence,
514 // we let the code fall through to the code at the bottom of the
515 // function that executes the task right there and then,
516 // synchronously.
517 //
518 // In practice, this means that one can't use task-based programming
519 // in a nested way. That's unfortunate but it is what it is for
520 // now.
521 if (MultithreadInfo::get_taskflow_executor().this_worker_id() < 0)
522 {
523 task_data = std::make_shared<TaskData>(
525 function_object));
526 return;
527 }
528#elif defined(DEAL_II_WITH_TBB)
529 // Create a promise object and from it extract a future that
530 // we can use to refer to the outcome of the task. For reasons
531 // explained below, we can't just create a std::promise object,
532 // but have to make do with a pointer to such an object.
533 std::unique_ptr<std::promise<RT>> promise =
534 std::make_unique<std::promise<RT>>();
535 task_data =
536 std::make_shared<TaskData>(std::move(promise->get_future()));
537
538 // Then start the task, using a task_group object (for just this one
539 // task) that is associated with the TaskData object. Note that we
540 // have to *copy* the function object being executed so that it is
541 // guaranteed to live on the called thread as well -- the copying is
542 // facilitated by capturing the 'function_object' variable by value.
543 //
544 // We also have to *move* the promise object into the new task's
545 // memory space because promises can not be copied and we can't refer
546 // to it by reference because it's a local variable of the current
547 // (surrounding) function that may go out of scope before the promise
548 // is ultimately set. This leads to a conundrum: if we had just
549 // declared 'promise' as an object of type std::promise, then we could
550 // capture it in the lambda function via
551 // [..., promise=std::move(promise)]() {...}
552 // and set the promise in the body of the lambda. But setting a
553 // promise is a non-const operation on the promise, and so we would
554 // actually have to declare the lambda function as 'mutable' because
555 // by default, lambda captures are 'const'. That is, we would have
556 // to write
557 // [..., promise=std::move(promise)]() mutable {...}
558 // But this leads to other problems: It turns out that the
559 // tbb::task_group::run() function cannot take mutable lambdas as
560 // argument :-(
561 //
562 // We work around this issue by not declaring the 'promise' variable
563 // as an object of type std::promise, but as a pointer to such an
564 // object. This pointer we can move, and the *pointer* itself can
565 // be 'const' (meaning we can leave the lambda as non-mutable)
566 // even though we modify the object *pointed to*. One would think
567 // that a std::unique_ptr would be the right choice for this, but
568 // that's not true: the resulting lambda function can then be
569 // non-mutable, but the lambda function object is not copyable
570 // and at least some TBB variants require that as well. So
571 // instead we move the std::unique_ptr used above into a
572 // std::shared_ptr to be stored within the lambda function object.
573 task_data->task_group->run(
574 [function_object,
575 promise =
576 std::shared_ptr<std::promise<RT>>(std::move(promise))]() {
577 try
578 {
579 internal::evaluate_and_set_promise(function_object, *promise);
580 }
581 catch (...)
582 {
583 try
584 {
585 // store anything thrown in the promise
586 promise->set_exception(std::current_exception());
587 }
588 catch (...)
589 {
590 // set_exception() may throw too. But ignore this on
591 // the task.
592 }
593 }
594 });
595 return;
596
597#else
598 // If no threading library is supported, just fall back onto C++11
599 // facilities. The problem with this is that the standard does
600 // not actually say what std::async should do. The first
601 // argument to that function can be std::launch::async or
602 // std::launch::deferred, or both. The *intent* of the standard's
603 // authors was probably that if one sets it to
604 // std::launch::async | std::launch::deferred,
605 // that the task is run in a thread pool. But at least as of
606 // 2021, GCC doesn't do that: It just runs it on a new thread.
607 // If one chooses std::launch::deferred, it runs the task on
608 // the same thread but only when one calls join() on the task's
609 // std::future object. In the former case, this leads to
610 // oversubscription, in the latter case to undersubscription of
611 // resources. We choose oversubscription here.
612 //
613 // The issue illustrates why relying on external libraries
614 // with task schedulers is the way to go.
615 task_data = std::make_shared<TaskData>(
616 std::async(std::launch::async | std::launch::deferred,
617 function_object));
618 return;
619#endif
620 }
621 {
622 // Only one thread allowed. So let the task run to completion
623 // and just emplace a 'ready' future.
624 //
625 // The design of std::promise/std::future is unclear, but it
626 // seems that the intent is to obtain the std::future before
627 // we set the std::promise. So create the TaskData object at
628 // the top and then run the task and set the returned
629 // value. Since everything here happens sequentially, it
630 // really doesn't matter in which order all of this is
631 // happening.
632 std::promise<RT> promise;
633 task_data = std::make_shared<TaskData>(promise.get_future());
634 try
635 {
636 internal::evaluate_and_set_promise(function_object, promise);
637 }
638 catch (...)
639 {
640 try
641 {
642 // store anything thrown in the promise
643 promise.set_exception(std::current_exception());
644 }
645 catch (...)
646 {
647 // set_exception() may throw too. But ignore this on
648 // the task.
649 }
650 }
651 }
652 }
653
662 Task() = default;
663
676 Task(const Task &other) = default;
677
691 Task(Task &&other) noexcept = default;
692
706 Task &
707 operator=(const Task &other) = default;
708
723 Task &
724 operator=(Task &&other) noexcept = default;
725
757 void
758 join() const
759 {
760 // Make sure we actually have a task that we can wait for.
762
763 task_data->wait();
764 }
765
778 bool
779 joinable() const
780 {
781 return (task_data != nullptr);
782 }
783
784
836 {
837 // Make sure we actually have a task that we can wait for.
839
840 // Then return the promised object. If necessary, wait for the promise to
841 // be set.
842 return task_data->get();
843 }
844
845
855 "The current object is not associated with a task that "
856 "can be joined. It may have been detached, or you "
857 "may have already joined it in the past.");
859 private:
869 {
870 public:
875 TaskData(std::future<RT> &&future) noexcept
876 : future(std::move(future))
877 , task_has_finished(false)
878#ifdef DEAL_II_WITH_TBB
879 , task_group(std::make_unique<tbb::task_group>())
880#endif
881 {}
882
887 TaskData(const TaskData &) = delete;
888
893 TaskData(TaskData &&) = delete;
894
899 TaskData &
900 operator=(const TaskData &) = delete;
901
906 TaskData &
907 operator=(TaskData &&) = delete;
908
916 ~TaskData() noexcept
917 {
918 // Explicitly wait for the results to be ready. This class stores
919 // a std::future object, and we could just let the compiler generate
920 // the destructor which would then call the destructor of std::future
921 // which *may* block until the future is ready. As explained in
922 // https://en.cppreference.com/w/cpp/thread/future/~future
923 // this is only a *may*, not a *must*. (The standard does not
924 // appear to say anything about it at all.) As a consequence,
925 // let's be explicit about waiting.
926 //
927 // One of the corner cases we have to worry about is that if a task
928 // ends by throwing an exception, then wait() will re-throw that
929 // exception on the thread that calls it, the first time around
930 // someone calls wait() (or the return_value() function of the
931 // surrounding class). So if we get to this constructor and an exception
932 // is thrown by wait(), then that means that the last Task object
933 // referring to a task is going out of scope with nobody having
934 // ever checked the return value of the task itself. In that case,
935 // one could argue that they would also not have cared about whether
936 // an exception is thrown, and that we should simply ignore the
937 // exception. This is what we do here. It is also the simplest solution,
938 // because we don't know what one should do with the exception to begin
939 // with: destructors aren't allowed to throw exceptions, so we can't
940 // just rethrow it here if one had been triggered.
941 try
942 {
943 wait();
944 }
945 catch (...)
946 {}
947 }
948
954 void
956 {
957 // If we have previously already moved the result, then we don't
958 // need a lock and can just return.
960 return;
961
962 // Else, we need to go under a lock and try again. A different thread
963 // may have waited and finished the task since then, so we have to try
964 // a second time. (This is Schmidt's double-checking pattern.)
965 std::scoped_lock lock(mutex);
967 return;
968 else
969 {
970#ifdef DEAL_II_WITH_TASKFLOW
971 // We want to call executor.corun_until() to keep scheduling tasks
972 // until the task we are waiting for has actually finished. The
973 // problem is that TaskFlow documents that you can only call
974 // corun_until() on a worker of the executor. In other words, we
975 // can call it from *inside* other tasks, but not from the main
976 // thread (or other threads that might have been created outside
977 // of TaskFlow).
978 //
979 // Fortunately, we can check whether we are on a worker thread:
980 if (MultithreadInfo::get_taskflow_executor().this_worker_id() >= 0)
981 MultithreadInfo::get_taskflow_executor().corun_until([this]() {
982 return (future.wait_for(std::chrono::seconds(0)) ==
983 std::future_status::ready);
984 });
985 else
986 // We are on a thread not managed by TaskFlow. In that case, we
987 // can simply stop the current thread to wait for the task to
988 // finish (i.e., for the std::future object to become ready). We
989 // can do this because we need not fear that this leads to a
990 // deadlock: The current threads is waiting for completion of a
991 // task that is running on a completely different set of
992 // threads, and so not making any progress here can not deprive
993 // these other threads of the ability to schedule their tasks.
994 //
995 // Indeed, this is even true if the current thread is a worker
996 // of one executor and we are waiting for a task running on a
997 // different executor: The current task being stopped may block
998 // the current executor from scheduling more tasks, but it is
999 // unrelated to the tasks of the scheduler for which we are
1000 // waiting for something, and so that other executor will
1001 // eventually get around to scheduling the task we are waiting
1002 // for, at which point the current task will also complete.
1003 future.wait();
1004
1005#elif defined(DEAL_II_WITH_TBB)
1006 // If we build on the TBB, then we can't just wait for the
1007 // std::future object to get ready. Apparently the TBB happily
1008 // enqueues a task into an arena and then just sits on it without
1009 // ever executing it unless someone expresses an interest in the
1010 // task. The way to avoid this is to add the task to a
1011 // tbb::task_group, and then here wait for the single task
1012 // associated with that task group.
1013 //
1014 // This also makes sense from another perspective. Imagine that
1015 // we allow at most N threads, and that we create N+1 tasks in such
1016 // a way that the first N all wait for the (N+1)st task to finish.
1017 // (See the multithreading/task_17 test for an example.) If they
1018 // all just sit in their std::future::wait() function, nothing
1019 // is ever going to happen because the scheduler sees that N tasks
1020 // are currently running and is never informed that all they're
1021 // doing is wait for another task to finish. What *needs* to
1022 // happen is that the wait() or join() function goes back into
1023 // the scheduler to make sure the scheduler knows that these
1024 // tasks are not actually using CPU time on the thread they're
1025 // working on, and that it is time to run other tasks on the
1026 // same thread -- this is the way we can eventually get that
1027 // (N+1)st task executed, which then unblocks the other N threads.
1028 // (Note that this also implies that multiple tasks can be
1029 // executing on the same thread at the same time -- not
1030 // concurrently, of course, but with one executing and the others
1031 // currently waiting for other tasks to finish.)
1032 //
1033 // If we get here, we know for a fact that atomically
1034 // (because under a lock), no other thread has so far
1035 // determined that we are finished and removed the
1036 // 'task_group' object. So we know that the pointer is
1037 // still valid. But we also know that, because below we
1038 // set the task_has_finished flag to 'true', that no other
1039 // thread will ever get back to this point and query the
1040 // 'task_group' object, so we can delete it.
1041 task_group->wait();
1042 task_group.reset();
1043#endif
1044
1045 // Wait for the task to finish and then move its
1046 // result. (We could have made the set_from() function
1047 // that we call here wait for the future to be ready --
1048 // which happens implicitly when it calls future.get() --
1049 // but that would have required putting an explicit
1050 // future.wait() into the implementation of
1051 // internal::return_value<void>::set_from(), which is a
1052 // bit awkward: that class doesn't actually need to set
1053 // anything, and so it looks odd to have the explicit call
1054 // to future.wait() in the set_from() function. Avoid the
1055 // issue by just explicitly calling future.wait() here.)
1056 future.wait();
1057
1058 // Acquire the returned object. If the task ended in an
1059 // exception, `set_from` will call `std::future::get`, which
1060 // will throw an exception. This leaves `returned_object` in
1061 // an undefined state, but moreover we would bypass setting
1062 // `task_has_finished=true` below. So catch the exception
1063 // for just long enough that we can set that flag, and then
1064 // re-throw it:
1065 try
1066 {
1067 returned_object.set_from(future);
1068 }
1069 catch (...)
1070 {
1071 task_has_finished = true;
1072 throw;
1073 }
1074
1075 // If we got here, the task has ended without an exception and
1076 // we can safely set the flag and return.
1077 task_has_finished = true;
1078 }
1079 }
1080
1081
1082
1085 {
1086 wait();
1087 return returned_object.get();
1088 }
1089
1090 private:
1095 std::mutex mutex;
1096
1101 std::future<RT> future;
1102
1121 std::atomic<bool> task_has_finished;
1122
1128
1129#ifdef DEAL_II_WITH_TBB
1137 std::unique_ptr<tbb::task_group> task_group;
1138
1139 friend class Task<RT>;
1140#endif
1141 };
1142
1147 std::shared_ptr<TaskData> task_data;
1148 };
1149
1150
1151
1171 template <typename RT>
1172 inline Task<RT>
1173 new_task(const std::function<RT()> &function)
1174 {
1175 return Task<RT>(function);
1176 }
1177
1178
1179
1257 template <typename FunctionObjectType>
1258 DEAL_II_CXX20_REQUIRES((std::invocable<FunctionObjectType>))
1259 inline auto new_task(FunctionObjectType function_object)
1260 -> Task<decltype(function_object())>
1261 {
1262 using return_type = decltype(function_object());
1264 return new_task(std::function<return_type()>(function_object));
1265 }
1266
1267
1268
1275 template <typename RT, typename... Args>
1276 inline Task<RT>
1277 new_task(RT (*fun_ptr)(Args...), std_cxx20::type_identity_t<Args>... args)
1278 {
1279 auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1280 return new_task(
1281 [dummy, fun_ptr]() -> RT { return std::apply(fun_ptr, dummy); });
1282 }
1283
1284
1285
1326 template <
1327 typename FunctionObject,
1328 typename... Args,
1329 typename = std::enable_if_t<std::is_invocable_v<FunctionObject, Args...>>,
1330 typename = std::enable_if_t<std::is_function_v<FunctionObject> == false>,
1331 typename =
1332 std::enable_if_t<std::is_member_pointer_v<FunctionObject> == false>,
1333 typename = std::enable_if_t<std::is_pointer_v<FunctionObject> == false>>
1334 inline Task<std::invoke_result_t<FunctionObject, Args...>>
1335 new_task(const FunctionObject &fun, Args &&...args)
1336 {
1337 using RT = std::invoke_result_t<FunctionObject, Args...>;
1338 auto dummy = std::make_tuple(std::forward<Args>(args)...);
1339 return new_task([dummy, fun]() -> RT { return std::apply(fun, dummy); });
1340 }
1341
1342
1343
1350 template <typename RT, typename C, typename... Args>
1351 inline Task<RT>
1352 new_task(RT (C::*fun_ptr)(Args...),
1353 std_cxx20::type_identity_t<C> &c,
1354 std_cxx20::type_identity_t<Args>... args)
1355 {
1356 // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1357 return new_task(std::function<RT()>(std::bind(
1358 fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1359 }
1360
1367 template <typename RT, typename C, typename... Args>
1368 inline Task<RT>
1369 new_task(RT (C::*fun_ptr)(Args...) const,
1370 std_cxx20::type_identity_t<const C> &c,
1371 std_cxx20::type_identity_t<Args>... args)
1372 {
1373 // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1374 return new_task(std::function<RT()>(std::bind(
1375 fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1376 }
1377
1378
1379 // ------------------------ TaskGroup -------------------------------------
1380
1391 template <typename RT = void>
1393 {
1394 public:
1398 TaskGroup &
1400 {
1401 tasks.push_back(t);
1402 return *this;
1403 }
1404
1405
1413 std::size_t
1414 size() const
1415 {
1416 return tasks.size();
1417 }
1418
1433 std::vector<RT>
1435 {
1436 std::vector<RT> results;
1437 results.reserve(size());
1438 for (auto &t : tasks)
1439 results.emplace_back(std::move(t.return_value()));
1440 return results;
1441 }
1442
1443
1450 void
1451 join_all() const
1452 {
1453 for (const auto &t : tasks)
1454 t.join();
1455 }
1456
1457 private:
1461 std::list<Task<RT>> tasks;
1462 };
1463
1464} // namespace Threads
1465
1472#endif
*  iterator end()
*  *  iterator begin()
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:38
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:249
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
Point< 2 > second
Definition grid_out.cc:4640
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)
typename type_identity< T >::type type_identity_t
Definition type_traits.h:93
STL namespace.
static std::reference_wrapper< T > act(T &t)
void set_from(std::future< RT & > &v)
void set_from(std::future< RT > &v)