Reference documentation for deal.II version GIT relicensing-1062-gc06da148b8 2024-07-15 19:20:02+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: 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>
25
26#ifdef DEAL_II_WITH_TASKFLOW
27# include <taskflow/taskflow.hpp>
28#endif
29
30#include <atomic>
31#include <functional>
32#include <future>
33#include <list>
34#include <memory>
35#include <thread>
36#include <tuple>
37#include <utility>
38#include <vector>
39
40#ifdef DEAL_II_HAVE_CXX20
41# include <concepts>
42#endif
43
44
45#ifdef DEAL_II_WITH_TBB
46# include <tbb/task_group.h>
47#endif
48
50
63namespace Threads
64{
79 template <typename ForwardIterator>
80 std::vector<std::pair<ForwardIterator, ForwardIterator>>
81 split_range(const ForwardIterator &begin,
82 const ForwardIterator &end,
83 const unsigned int n_intervals);
84
93 std::vector<std::pair<unsigned int, unsigned int>>
94 split_interval(const unsigned int begin,
95 const unsigned int end,
96 const unsigned int n_intervals);
97
107 namespace internal
108 {
124 [[noreturn]] void
125 handle_std_exception(const std::exception &exc);
126
134 [[noreturn]] void
136 } // namespace internal
137
142} // namespace Threads
143
144/* ----------- implementation of functions in namespace Threads ---------- */
145#ifndef DOXYGEN
146namespace Threads
147{
148 template <typename ForwardIterator>
149 std::vector<std::pair<ForwardIterator, ForwardIterator>>
150 split_range(const ForwardIterator &begin,
151 const ForwardIterator &end,
152 const unsigned int n_intervals)
153 {
154 using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
155
156 // in non-multithreaded mode, we often have the case that this
157 // function is called with n_intervals==1, so have a shortcut here
158 // to handle that case efficiently
159
160 if (n_intervals == 1)
161 return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
162
163 // if more than one interval requested, do the full work
164 const unsigned int n_elements = std::distance(begin, end);
165 const unsigned int n_elements_per_interval = n_elements / n_intervals;
166 const unsigned int residual = n_elements % n_intervals;
167
168 std::vector<IteratorPair> return_values(n_intervals);
169
170 return_values[0].first = begin;
171 for (unsigned int i = 0; i < n_intervals; ++i)
172 {
173 if (i != n_intervals - 1)
174 {
175 return_values[i].second = return_values[i].first;
176 // note: the cast is performed to avoid a warning of gcc
177 // that in the library `dist>=0' is checked (dist has a
178 // template type, which here is unsigned if no cast is
179 // performed)
180 std::advance(return_values[i].second,
181 static_cast<signed int>(n_elements_per_interval));
182 // distribute residual in division equally among the first
183 // few subintervals
184 if (i < residual)
185 ++return_values[i].second;
186
187 return_values[i + 1].first = return_values[i].second;
188 }
189 else
190 return_values[i].second = end;
191 }
192 return return_values;
193 }
194} // namespace Threads
195
196#endif // DOXYGEN
197
198namespace Threads
199{
200 namespace internal
201 {
220 template <typename RT>
222 {
223 private:
226
227 public:
228 using reference_type = RT &;
229
231 : value()
232 , value_is_initialized(false)
233 {}
234
235 inline reference_type
237 {
238 Assert(
241 "You cannot read the return value of a thread or task "
242 "if that value has not been set. This happens, for example, if "
243 "a task or thread threw an exception."));
244 return value;
245 }
246
247 inline void
248 set(RT &&v)
249 {
250 value = std::move(v);
251 }
252
258 inline void
259 set_from(std::future<RT> &v)
260 {
261 // Get the value from the std::future object. If the future holds
262 // an exception, then the assignment fails, we exit the function via the
263 // exception right away, and value_is_initialized is not set to true --
264 // that's something we can check later on.
265 value = std::move(v.get());
267 }
268 };
269
270
290 template <typename RT>
291 struct return_value<RT &>
292 {
293 private:
294 RT *value;
296
297 public:
298 using reference_type = RT &;
299
301 : value(nullptr)
302 , value_is_initialized(false)
303 {}
304
305 inline reference_type
306 get() const
307 {
308 Assert(
309 value_is_initialized,
311 "You cannot read the return value of a thread or task "
312 "if that value has not been set. This happens, for example, if "
313 "a task or thread threw an exception."));
314 return *value;
315 }
316
317 inline void
318 set(RT &v)
319 {
320 value = &v;
321 }
322
328 inline void
329 set_from(std::future<RT &> &v)
330 {
331 // Get the value from the std::future object. If the future holds
332 // an exception, then the assignment fails, we exit the function via the
333 // exception right away, and value_is_initialized is not set to true --
334 // that's something we can check later on.
335 value = &v.get();
336 value_is_initialized = true;
337 }
338 };
339
340
359 template <>
360 struct return_value<void>
361 {
362 using reference_type = void;
363
364 static inline void
366 {}
367
368
375 inline void
376 set_from(std::future<void> &)
377 {}
378 };
379 } // namespace internal
380
381
382
383 namespace internal
384 {
392 template <typename T>
394 {
395 static T
396 act(const T &t)
397 {
398 return t;
399 }
400 };
401
402
403
411 template <typename T>
412 struct maybe_make_ref<T &>
413 {
414 static std::reference_wrapper<T>
415 act(T &t)
416 {
417 return std::ref(t);
418 }
419 };
420
421
422
429 template <typename RT, typename Function>
431 (std::invocable<Function> &&
432 std::convertible_to<std::invoke_result_t<Function>, RT>))
433 void evaluate_and_set_promise(Function &function, std::promise<RT> &promise)
434 {
435 promise.set_value(function());
436 }
437
438
448 template <typename Function>
449 DEAL_II_CXX20_REQUIRES((std::invocable<Function>))
451 std::promise<void> &promise)
452 {
453 function();
454 promise.set_value();
455 }
456 } // namespace internal
457
458
459
487 template <typename RT = void>
488 class Task
489 {
490 public:
502 Task(const std::function<RT()> &function_object)
503 {
505 {
506#ifdef DEAL_II_WITH_TASKFLOW
507 task_data = std::make_shared<TaskData>(
508 MultithreadInfo::get_taskflow_executor().async(function_object));
509#elif defined(DEAL_II_WITH_TBB)
510 // Create a promise object and from it extract a future that
511 // we can use to refer to the outcome of the task. For reasons
512 // explained below, we can't just create a std::promise object,
513 // but have to make do with a pointer to such an object.
514 std::unique_ptr<std::promise<RT>> promise =
515 std::make_unique<std::promise<RT>>();
516 task_data =
517 std::make_shared<TaskData>(std::move(promise->get_future()));
518
519 // Then start the task, using a task_group object (for just this one
520 // task) that is associated with the TaskData object. Note that we
521 // have to *copy* the function object being executed so that it is
522 // guaranteed to live on the called thread as well -- the copying is
523 // facilitated by capturing the 'function_object' variable by value.
524 //
525 // We also have to *move* the promise object into the new task's
526 // memory space because promises can not be copied and we can't refer
527 // to it by reference because it's a local variable of the current
528 // (surrounding) function that may go out of scope before the promise
529 // is ultimately set. This leads to a conundrum: if we had just
530 // declared 'promise' as an object of type std::promise, then we could
531 // capture it in the lambda function via
532 // [..., promise=std::move(promise)]() {...}
533 // and set the promise in the body of the lambda. But setting a
534 // promise is a non-const operation on the promise, and so we would
535 // actually have to declare the lambda function as 'mutable' because
536 // by default, lambda captures are 'const'. That is, we would have
537 // to write
538 // [..., promise=std::move(promise)]() mutable {...}
539 // But this leads to other problems: It turns out that the
540 // tbb::task_group::run() function cannot take mutable lambdas as
541 // argument :-(
542 //
543 // We work around this issue by not declaring the 'promise' variable
544 // as an object of type std::promise, but as a pointer to such an
545 // object. This pointer we can move, and the *pointer* itself can
546 // be 'const' (meaning we can leave the lambda as non-mutable)
547 // even though we modify the object *pointed to*. One would think
548 // that a std::unique_ptr would be the right choice for this, but
549 // that's not true: the resulting lambda function can then be
550 // non-mutable, but the lambda function object is not copyable
551 // and at least some TBB variants require that as well. So
552 // instead we move the std::unique_ptr used above into a
553 // std::shared_ptr to be stored within the lambda function object.
554 task_data->task_group->run(
555 [function_object,
556 promise =
557 std::shared_ptr<std::promise<RT>>(std::move(promise))]() {
558 try
559 {
560 internal::evaluate_and_set_promise(function_object, *promise);
561 }
562 catch (...)
563 {
564 try
565 {
566 // store anything thrown in the promise
567 promise->set_exception(std::current_exception());
568 }
569 catch (...)
570 {
571 // set_exception() may throw too. But ignore this on
572 // the task.
573 }
574 }
575 });
576
577#else
578 // If no threading library is supported, just fall back onto C++11
579 // facilities. The problem with this is that the standard does
580 // not actually say what std::async should do. The first
581 // argument to that function can be std::launch::async or
582 // std::launch::deferred, or both. The *intent* of the standard's
583 // authors was probably that if one sets it to
584 // std::launch::async | std::launch::deferred,
585 // that the task is run in a thread pool. But at least as of
586 // 2021, GCC doesn't do that: It just runs it on a new thread.
587 // If one chooses std::launch::deferred, it runs the task on
588 // the same thread but only when one calls join() on the task's
589 // std::future object. In the former case, this leads to
590 // oversubscription, in the latter case to undersubscription of
591 // resources. We choose oversubscription here.
592 //
593 // The issue illustrates why relying on external libraries
594 // with task schedulers is the way to go.
595 task_data = std::make_shared<TaskData>(
596 std::async(std::launch::async | std::launch::deferred,
597 function_object));
598#endif
599 }
600 else
601 {
602 // Only one thread allowed. So let the task run to completion
603 // and just emplace a 'ready' future.
604 //
605 // The design of std::promise/std::future is unclear, but it
606 // seems that the intent is to obtain the std::future before
607 // we set the std::promise. So create the TaskData object at
608 // the top and then run the task and set the returned
609 // value. Since everything here happens sequentially, it
610 // really doesn't matter in which order all of this is
611 // happening.
612 std::promise<RT> promise;
613 task_data = std::make_shared<TaskData>(promise.get_future());
614 try
615 {
616 internal::evaluate_and_set_promise(function_object, promise);
617 }
618 catch (...)
619 {
620 try
621 {
622 // store anything thrown in the promise
623 promise.set_exception(std::current_exception());
624 }
625 catch (...)
626 {
627 // set_exception() may throw too. But ignore this on
628 // the task.
629 }
630 }
631 }
632 }
633
642 Task() = default;
643
656 Task(const Task &other) = default;
657
671 Task(Task &&other) noexcept = default;
672
686 Task &
687 operator=(const Task &other) = default;
688
703 Task &
704 operator=(Task &&other) noexcept = default;
705
737 void
738 join() const
739 {
740 // Make sure we actually have a task that we can wait for.
742
743 task_data->wait();
744 }
745
758 bool
759 joinable() const
760 {
761 return (task_data != nullptr);
762 }
763
764
816 {
817 // Make sure we actually have a task that we can wait for.
819
820 // Then return the promised object. If necessary, wait for the promise to
821 // be set.
822 return task_data->get();
823 }
824
825
835 "The current object is not associated with a task that "
836 "can be joined. It may have been detached, or you "
837 "may have already joined it in the past.");
839 private:
849 {
850 public:
855 TaskData(std::future<RT> &&future) noexcept
856 : future(std::move(future))
857 , task_has_finished(false)
858#ifdef DEAL_II_WITH_TBB
859 , task_group(std::make_unique<tbb::task_group>())
860#endif
861 {}
862
867 TaskData(const TaskData &) = delete;
868
873 TaskData(TaskData &&) = delete;
874
879 TaskData &
880 operator=(const TaskData &) = delete;
881
886 TaskData &
887 operator=(TaskData &&) = delete;
888
896 ~TaskData() noexcept
897 {
898 // Explicitly wait for the results to be ready. This class stores
899 // a std::future object, and we could just let the compiler generate
900 // the destructor which would then call the destructor of std::future
901 // which *may* block until the future is ready. As explained in
902 // https://en.cppreference.com/w/cpp/thread/future/~future
903 // this is only a *may*, not a *must*. (The standard does not
904 // appear to say anything about it at all.) As a consequence,
905 // let's be explicit about waiting.
906 //
907 // One of the corner cases we have to worry about is that if a task
908 // ends by throwing an exception, then wait() will re-throw that
909 // exception on the thread that calls it, the first time around
910 // someone calls wait() (or the return_value() function of the
911 // surrounding class). So if we get to this constructor and an exception
912 // is thrown by wait(), then that means that the last Task object
913 // referring to a task is going out of scope with nobody having
914 // ever checked the return value of the task itself. In that case,
915 // one could argue that they would also not have cared about whether
916 // an exception is thrown, and that we should simply ignore the
917 // exception. This is what we do here. It is also the simplest solution,
918 // because we don't know what one should do with the exception to begin
919 // with: destructors aren't allowed to throw exceptions, so we can't
920 // just rethrow it here if one had been triggered.
921 try
922 {
923 wait();
924 }
925 catch (...)
926 {}
927 }
928
934 void
936 {
937 // If we have previously already moved the result, then we don't
938 // need a lock and can just return.
940 return;
941
942 // Else, we need to go under a lock and try again. A different thread
943 // may have waited and finished the task since then, so we have to try
944 // a second time. (This is Schmidt's double-checking pattern.)
945 std::lock_guard<std::mutex> lock(mutex);
947 return;
948 else
949 {
950#ifdef DEAL_II_WITH_TASKFLOW
951 // We want to call executor.corun_until() to keep scheduling tasks
952 // until the task we are waiting for has actually finished. The
953 // problem is that TaskFlow documents that you can only call
954 // corun_until() on a worker of the executor. In other words, we
955 // can call it from *inside* other tasks, but not from the main
956 // thread (or other threads that might have been created outside
957 // of TaskFlow).
958 //
959 // Fortunately, we can check whether we are on a worker thread:
960 if (MultithreadInfo::get_taskflow_executor().this_worker_id() >= 0)
961 MultithreadInfo::get_taskflow_executor().corun_until([this]() {
962 return (future.wait_for(std::chrono::seconds(0)) ==
963 std::future_status::ready);
964 });
965 else
966 // We are on a thread not managed by TaskFlow. In that case, we
967 // can simply stop the current thread to wait for the task to
968 // finish (i.e., for the std::future object to become ready). We
969 // can do this because we need not fear that this leads to a
970 // deadlock: The current threads is waiting for completion of a
971 // task that is running on a completely different set of
972 // threads, and so not making any progress here can not deprive
973 // these other threads of the ability to schedule their tasks.
974 //
975 // Indeed, this is even true if the current thread is a worker
976 // of one executor and we are waiting for a task running on a
977 // different executor: The current task being stopped may block
978 // the current executor from scheduling more tasks, but it is
979 // unrelated to the tasks of the scheduler for which we are
980 // waiting for something, and so that other executor will
981 // eventually get arond to scheduling the task we are waiting
982 // for, at which point the current task will also complete.
983 future.wait();
984
985#elif defined(DEAL_II_WITH_TBB)
986 // If we build on the TBB, then we can't just wait for the
987 // std::future object to get ready. Apparently the TBB happily
988 // enqueues a task into an arena and then just sits on it without
989 // ever executing it unless someone expresses an interest in the
990 // task. The way to avoid this is to add the task to a
991 // tbb::task_group, and then here wait for the single task
992 // associated with that task group.
993 //
994 // This also makes sense from another perspective. Imagine that
995 // we allow at most N threads, and that we create N+1 tasks in such
996 // a way that the first N all wait for the (N+1)st task to finish.
997 // (See the multithreading/task_17 test for an example.) If they
998 // all just sit in their std::future::wait() function, nothing
999 // is ever going to happen because the scheduler sees that N tasks
1000 // are currently running and is never informed that all they're
1001 // doing is wait for another task to finish. What *needs* to
1002 // happen is that the wait() or join() function goes back into
1003 // the scheduler to make sure the scheduler knows that these
1004 // tasks are not actually using CPU time on the thread they're
1005 // working on, and that it is time to run other tasks on the
1006 // same thread -- this is the way we can eventually get that
1007 // (N+1)st task executed, which then unblocks the other N threads.
1008 // (Note that this also implies that multiple tasks can be
1009 // executing on the same thread at the same time -- not
1010 // concurrently, of course, but with one executing and the others
1011 // currently waiting for other tasks to finish.)
1012 //
1013 // If we get here, we know for a fact that atomically
1014 // (because under a lock), no other thread has so far
1015 // determined that we are finished and removed the
1016 // 'task_group' object. So we know that the pointer is
1017 // still valid. But we also know that, because below we
1018 // set the task_has_finished flag to 'true', that no other
1019 // thread will ever get back to this point and query the
1020 // 'task_group' object, so we can delete it.
1021 task_group->wait();
1022 task_group.reset();
1023#endif
1024
1025 // Wait for the task to finish and then move its
1026 // result. (We could have made the set_from() function
1027 // that we call here wait for the future to be ready --
1028 // which happens implicitly when it calls future.get() --
1029 // but that would have required putting an explicit
1030 // future.wait() into the implementation of
1031 // internal::return_value<void>::set_from(), which is a
1032 // bit awkward: that class doesn't actually need to set
1033 // anything, and so it looks odd to have the explicit call
1034 // to future.wait() in the set_from() function. Avoid the
1035 // issue by just explicitly calling future.wait() here.)
1036 future.wait();
1037
1038 // Acquire the returned object. If the task ended in an
1039 // exception, `set_from` will call `std::future::get`, which
1040 // will throw an exception. This leaves `returned_object` in
1041 // an undefined state, but moreover we would bypass setting
1042 // `task_has_finished=true` below. So catch the exception
1043 // for just long enough that we can set that flag, and then
1044 // re-throw it:
1045 try
1046 {
1047 returned_object.set_from(future);
1048 }
1049 catch (...)
1050 {
1051 task_has_finished = true;
1052 throw;
1053 }
1054
1055 // If we got here, the task has ended without an exception and
1056 // we can safely set the flag and return.
1057 task_has_finished = true;
1058 }
1059 }
1060
1061
1062
1065 {
1066 wait();
1067 return returned_object.get();
1068 }
1069
1070 private:
1075 std::mutex mutex;
1076
1081 std::future<RT> future;
1082
1101 std::atomic<bool> task_has_finished;
1102
1108
1109#ifdef DEAL_II_WITH_TBB
1117 std::unique_ptr<tbb::task_group> task_group;
1118
1119 friend class Task<RT>;
1120#endif
1121 };
1122
1127 std::shared_ptr<TaskData> task_data;
1128 };
1129
1130
1131
1151 template <typename RT>
1152 inline Task<RT>
1153 new_task(const std::function<RT()> &function)
1154 {
1155 return Task<RT>(function);
1156 }
1157
1158
1159
1237 template <typename FunctionObjectType>
1238 DEAL_II_CXX20_REQUIRES((std::invocable<FunctionObjectType>))
1239 inline auto new_task(FunctionObjectType function_object)
1240 -> Task<decltype(function_object())>
1241 {
1242 using return_type = decltype(function_object());
1244 return new_task(std::function<return_type()>(function_object));
1245 }
1246
1247
1248
1255 template <typename RT, typename... Args>
1256 inline Task<RT>
1257 new_task(RT (*fun_ptr)(Args...), std_cxx20::type_identity_t<Args>... args)
1258 {
1259 auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1260 return new_task(
1261 [dummy, fun_ptr]() -> RT { return std::apply(fun_ptr, dummy); });
1262 }
1263
1264
1265
1306 template <
1307 typename FunctionObject,
1308 typename... Args,
1309 typename = std::enable_if_t<std::is_invocable_v<FunctionObject, Args...>>,
1310 typename = std::enable_if_t<std::is_function_v<FunctionObject> == false>,
1311 typename =
1312 std::enable_if_t<std::is_member_pointer_v<FunctionObject> == false>,
1313 typename = std::enable_if_t<std::is_pointer_v<FunctionObject> == false>>
1314 inline Task<std::invoke_result_t<FunctionObject, Args...>>
1315 new_task(const FunctionObject &fun, Args &&...args)
1316 {
1317 using RT = std::invoke_result_t<FunctionObject, Args...>;
1318 auto dummy = std::make_tuple(std::forward<Args>(args)...);
1319 return new_task([dummy, fun]() -> RT { return std::apply(fun, dummy); });
1320 }
1321
1322
1323
1330 template <typename RT, typename C, typename... Args>
1331 inline Task<RT>
1332 new_task(RT (C::*fun_ptr)(Args...),
1333 std_cxx20::type_identity_t<C> &c,
1334 std_cxx20::type_identity_t<Args>... args)
1335 {
1336 // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1337 return new_task(std::function<RT()>(std::bind(
1338 fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1339 }
1340
1347 template <typename RT, typename C, typename... Args>
1348 inline Task<RT>
1349 new_task(RT (C::*fun_ptr)(Args...) const,
1350 std_cxx20::type_identity_t<const C> &c,
1351 std_cxx20::type_identity_t<Args>... args)
1352 {
1353 // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1354 return new_task(std::function<RT()>(std::bind(
1355 fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1356 }
1357
1358
1359 // ------------------------ TaskGroup -------------------------------------
1360
1371 template <typename RT = void>
1373 {
1374 public:
1378 TaskGroup &
1380 {
1381 tasks.push_back(t);
1382 return *this;
1383 }
1384
1385
1393 std::size_t
1394 size() const
1395 {
1396 return tasks.size();
1397 }
1398
1413 std::vector<RT>
1415 {
1416 std::vector<RT> results;
1417 results.reserve(size());
1418 for (auto &t : tasks)
1419 results.emplace_back(std::move(t.return_value()));
1420 return results;
1421 }
1422
1423
1430 void
1431 join_all() const
1432 {
1433 for (const auto &t : tasks)
1434 t.join();
1435 }
1436
1437 private:
1441 std::list<Task<RT>> tasks;
1442 };
1443
1444} // namespace Threads
1445
1452#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:502
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:177
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:503
Point< 2 > second
Definition grid_out.cc:4624
static ::ExceptionBase & ExcNoTask()
#define Assert(cond, exc)
#define DeclExceptionMsg(Exception, defaulttext)
Definition exceptions.h:494
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)