Reference documentation for deal.II version GIT b8135fa6eb 2023-03-25 15:55: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\}}\)
thread_management.h
Go to the documentation of this file.
1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2000 - 2022 by the deal.II authors
4 //
5 // This file is part of the deal.II library.
6 //
7 // The deal.II library is free software; you can use it, redistribute
8 // it, and/or modify it under the terms of the GNU Lesser General
9 // Public License as published by the Free Software Foundation; either
10 // version 2.1 of the License, or (at your option) any later version.
11 // The full text of the license can be found in the file LICENSE.md at
12 // the top level directory of deal.II.
13 //
14 // ---------------------------------------------------------------------
15 
16 #ifndef dealii_thread_management_h
17 #define dealii_thread_management_h
18 
19 
20 #include <deal.II/base/config.h>
21 
24 #include <deal.II/base/mutex.h>
27 
28 #include <atomic>
29 #include <functional>
30 #include <future>
31 #include <list>
32 #include <memory>
33 #include <thread>
34 #include <utility>
35 #include <vector>
36 
37 #ifdef DEAL_II_HAVE_CXX20
38 # include <concepts>
39 #endif
40 
41 
42 #ifdef DEAL_II_WITH_TBB
43 # include <tbb/task_group.h>
44 #endif
45 
47 
60 namespace Threads
61 {
76  template <typename ForwardIterator>
77  std::vector<std::pair<ForwardIterator, ForwardIterator>>
78  split_range(const ForwardIterator &begin,
79  const ForwardIterator &end,
80  const unsigned int n_intervals);
81 
90  std::vector<std::pair<unsigned int, unsigned int>>
91  split_interval(const unsigned int begin,
92  const unsigned int end,
93  const unsigned int n_intervals);
94 
104  namespace internal
105  {
121  [[noreturn]] void
122  handle_std_exception(const std::exception &exc);
123 
131  [[noreturn]] void
133  } // namespace internal
134 
139 } // namespace Threads
140 
141 /* ----------- implementation of functions in namespace Threads ---------- */
142 #ifndef DOXYGEN
143 namespace Threads
144 {
145  template <typename ForwardIterator>
146  std::vector<std::pair<ForwardIterator, ForwardIterator>>
147  split_range(const ForwardIterator &begin,
148  const ForwardIterator &end,
149  const unsigned int n_intervals)
150  {
151  using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
152 
153  // in non-multithreaded mode, we often have the case that this
154  // function is called with n_intervals==1, so have a shortcut here
155  // to handle that case efficiently
156 
157  if (n_intervals == 1)
158  return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
159 
160  // if more than one interval requested, do the full work
161  const unsigned int n_elements = std::distance(begin, end);
162  const unsigned int n_elements_per_interval = n_elements / n_intervals;
163  const unsigned int residual = n_elements % n_intervals;
164 
165  std::vector<IteratorPair> return_values(n_intervals);
166 
167  return_values[0].first = begin;
168  for (unsigned int i = 0; i < n_intervals; ++i)
169  {
170  if (i != n_intervals - 1)
171  {
172  return_values[i].second = return_values[i].first;
173  // note: the cast is performed to avoid a warning of gcc
174  // that in the library `dist>=0' is checked (dist has a
175  // template type, which here is unsigned if no cast is
176  // performed)
177  std::advance(return_values[i].second,
178  static_cast<signed int>(n_elements_per_interval));
179  // distribute residual in division equally among the first
180  // few subintervals
181  if (i < residual)
182  ++return_values[i].second;
183 
184  return_values[i + 1].first = return_values[i].second;
185  }
186  else
187  return_values[i].second = end;
188  }
189  return return_values;
190  }
191 } // namespace Threads
192 
193 #endif // DOXYGEN
194 
195 namespace Threads
196 {
197  namespace internal
198  {
217  template <typename RT>
219  {
220  private:
221  RT value;
222 
223  public:
224  using reference_type = RT &;
225 
226  inline return_value()
227  : value()
228  {}
229 
230  inline reference_type
231  get()
232  {
233  return value;
234  }
235 
236  inline void
237  set(RT &&v)
238  {
239  value = std::move(v);
240  }
241 
242  inline void
243  set_from(std::future<RT> &v)
244  {
245  value = std::move(v.get());
246  }
247  };
248 
249 
269  template <typename RT>
270  struct return_value<RT &>
271  {
272  private:
273  RT *value;
274 
275  public:
276  using reference_type = RT &;
277 
278  inline return_value()
279  : value(nullptr)
280  {}
281 
282  inline reference_type
283  get() const
284  {
285  return *value;
286  }
287 
288  inline void
289  set(RT &v)
290  {
291  value = &v;
292  }
293 
294  inline void
295  set_from(std::future<RT &> &v)
296  {
297  value = &v.get();
298  }
299  };
300 
301 
320  template <>
321  struct return_value<void>
322  {
323  using reference_type = void;
324 
325  static inline void
326  get()
327  {}
328 
329 
330  inline void
331  set_from(std::future<void> &)
332  {}
333  };
334  } // namespace internal
335 
336 
337 
338  namespace internal
339  {
340  template <typename RT>
341  inline void
342  call(const std::function<RT()> & function,
344  {
345  ret_val.set(function());
346  }
347 
348 
349  inline void
350  call(const std::function<void()> &function, internal::return_value<void> &)
351  {
352  function();
353  }
354  } // namespace internal
355 
356 
357 
358  namespace internal
359  {
370  template <typename RT>
372  {
376  std::thread thread;
377 
386  std::shared_ptr<return_value<RT>> ret_val;
387 
423  std::atomic<bool> thread_is_active;
424 
429 
434  : thread_is_active(false)
435  {}
436 
438  {
439  if (!thread_is_active)
440  return;
441  thread.detach();
442  thread_is_active = false;
443  }
444 
449  void
450  start(const std::function<RT()> &function)
451  {
452  thread_is_active = true;
453  ret_val = std::make_shared<return_value<RT>>();
454  thread = std::thread(thread_entry_point, function, ret_val);
455  }
456 
457 
461  void
463  {
464  // see if the thread hasn't been joined yet. if it has, then
465  // join() is a no-op. use schmidt's double-checking strategy
466  // to use the mutex only when necessary
467  if (thread_is_active == false)
468  return;
469 
470  std::lock_guard<std::mutex> lock(thread_is_active_mutex);
471  if (thread_is_active == true)
472  {
473  Assert(thread.joinable(), ExcInternalError());
474  thread.join();
475  thread_is_active = false;
476  }
477  }
478 
479  private:
483  static void
484  thread_entry_point(const std::function<RT()> & function,
485  std::shared_ptr<return_value<RT>> ret_val)
486  {
487  // call the function in question. since an exception that is
488  // thrown from one of the called functions will not propagate
489  // to the main thread, it will kill the program if not treated
490  // here before we return to the operating system's thread
491  // library
492  try
493  {
494  call(function, *ret_val);
495  }
496  catch (const std::exception &exc)
497  {
499  }
500  catch (...)
501  {
503  }
504  }
505  };
506  } // namespace internal
507 
508 
535  template <typename RT = void>
536  class Thread
537  {
538  public:
543  Thread(const std::function<RT()> &function)
544  : thread_descriptor(new internal::ThreadDescriptor<RT>())
545  {
546  // in a second step, start the thread.
547  thread_descriptor->start(function);
548  }
549 
556  Thread() = default;
557 
562  Thread(const Thread<RT> &t)
564  {}
565 
571  void
572  join() const
573  {
574  if (thread_descriptor)
575  thread_descriptor->join();
576  }
577 
623  {
624  join();
625  return thread_descriptor->ret_val->get();
626  }
627 
632  bool
633  valid() const
634  {
635  return static_cast<bool>(thread_descriptor);
636  }
637 
638 
644  bool
645  operator==(const Thread &t) const
646  {
648  }
649 
650  private:
656  std::shared_ptr<internal::ThreadDescriptor<RT>> thread_descriptor;
657  };
658 
659 
660  namespace internal
661  {
669  template <typename T>
671  {
672  static T
673  act(T &t)
674  {
675  return t;
676  }
677  };
678 
679 
680 
688  template <typename T>
689  struct maybe_make_ref<T &>
690  {
691  static std::reference_wrapper<T>
692  act(T &t)
693  {
694  return std::ref(t);
695  }
696  };
697  } // namespace internal
698 
699 
700 
701  // ----------- thread starters for functions not taking any parameters
702 
713  template <typename RT>
714  DEAL_II_DEPRECATED inline Thread<RT>
715  new_thread(const std::function<RT()> &function)
716  {
717  // Here and below we need to disable deprecation warnings for calling the
718  // constructor in this function - as this function itself is deprecated
719  // these warnings are not helpful. This problem only appears in some
720  // configurations (e.g., Debian 11 with GCC-10).
722  return Thread<RT>(function);
724  }
725 
726 
727 
794  template <typename FunctionObjectType>
795  DEAL_II_CXX20_REQUIRES((std::invocable<FunctionObjectType>))
796  DEAL_II_DEPRECATED inline auto new_thread(FunctionObjectType function_object)
797  -> Thread<decltype(function_object())>
798  {
799  // See the comment in the first new_thread() implementation
801  using return_type = decltype(function_object());
802  return Thread<return_type>(std::function<return_type()>(function_object));
804  }
805 
806 
807 
816  template <typename RT, typename... Args>
817  DEAL_II_DEPRECATED inline Thread<RT>
818  new_thread(RT (*fun_ptr)(Args...), std_cxx20::type_identity_t<Args>... args)
819  {
820  // See the comment in the first new_thread() implementation
822  auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
823  return new_thread(
824  [dummy, fun_ptr]() -> RT { return std_cxx17::apply(fun_ptr, dummy); });
826  }
827 
828 
829 
837  template <typename RT, typename C, typename... Args>
838  DEAL_II_DEPRECATED inline Thread<RT>
839  new_thread(RT (C::*fun_ptr)(Args...),
842  {
843  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
844  return new_thread(std::function<RT()>(std::bind(
845  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
846  }
847 
855  template <typename RT, typename C, typename... Args>
856  DEAL_II_DEPRECATED inline Thread<RT>
857  new_thread(RT (C::*fun_ptr)(Args...) const,
860  {
861  // See the comment in the first new_thread() implementation
863  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
864  return new_thread(std::function<RT()>(std::bind(
865  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
867  }
868 
869  // ------------------------ ThreadGroup -------------------------------------
870 
880  template <typename RT = void>
882  {
883  public:
887  ThreadGroup &
889  {
890  threads.push_back(t);
891  return *this;
892  }
893 
900  void
901  join_all() const
902  {
903  for (auto &t : threads)
904  t.join();
905  }
906 
907  private:
911  std::list<Thread<RT>> threads;
912  };
913 
914 
915  namespace internal
916  {
920  template <typename RT, typename Function>
922  (std::invocable<Function> &&
923  std::convertible_to<std::invoke_result_t<Function>, RT>))
924  void evaluate_and_set_promise(Function &function, std::promise<RT> &promise)
925  {
926  promise.set_value(function());
927  }
928 
929 
937  template <typename Function>
938  DEAL_II_CXX20_REQUIRES((std::invocable<Function>))
940  std::promise<void> &promise)
941  {
942  function();
943  promise.set_value();
944  }
945  } // namespace internal
946 
947 
948 
975  template <typename RT = void>
976  class Task
977  {
978  public:
990  Task(const std::function<RT()> &function_object)
991  {
992  if (MultithreadInfo::n_threads() > 1)
993  {
994 #ifdef DEAL_II_WITH_TBB
995  // Create a promise object and from it extract a future that
996  // we can use to refer to the outcome of the task. For reasons
997  // explained below, we can't just create a std::promise object,
998  // but have to make do with a pointer to such an object.
999  std::unique_ptr<std::promise<RT>> promise =
1000  std::make_unique<std::promise<RT>>();
1001  task_data =
1002  std::make_shared<TaskData>(std::move(promise->get_future()));
1003 
1004  // Then start the task, using a task_group object (for just this one
1005  // task) that is associated with the TaskData object. Note that we
1006  // have to *copy* the function object being executed so that it is
1007  // guaranteed to live on the called thread as well -- the copying is
1008  // facilitated by capturing the 'function_object' variable by value.
1009  //
1010  // We also have to *move* the promise object into the new task's
1011  // memory space because promises can not be copied and we can't refer
1012  // to it by reference because it's a local variable of the current
1013  // (surrounding) function that may go out of scope before the promise
1014  // is ultimately set. This leads to a conundrum: if we had just
1015  // declared 'promise' as an object of type std::promise, then we could
1016  // capture it in the lambda function via
1017  // [..., promise=std::move(promise)]() {...}
1018  // and set the promise in the body of the lambda. But setting a
1019  // promise is a non-const operation on the promise, and so we would
1020  // actually have to declare the lambda function as 'mutable' because
1021  // by default, lambda captures are 'const'. That is, we would have
1022  // to write
1023  // [..., promise=std::move(promise)]() mutable {...}
1024  // But this leads to other problems: It turns out that the
1025  // tbb::task_group::run() function cannot take mutable lambdas as
1026  // argument :-(
1027  //
1028  // We work around this issue by not declaring the 'promise' variable
1029  // as an object of type std::promise, but as a pointer to such an
1030  // object. This pointer we can move, and the *pointer* itself can
1031  // be 'const' (meaning we can leave the lambda as non-mutable)
1032  // even though we modify the object *pointed to*. One would think
1033  // that a std::unique_ptr would be the right choice for this, but
1034  // that's not true: the resulting lambda function can then be
1035  // non-mutable, but the lambda function object is not copyable
1036  // and at least some TBB variants require that as well. So
1037  // instead we move the std::unique_ptr used above into a
1038  // std::shared_ptr to be stored within the lambda function object.
1039  task_data->task_group.run(
1040  [function_object,
1041  promise =
1042  std::shared_ptr<std::promise<RT>>(std::move(promise))]() {
1043  try
1044  {
1045  internal::evaluate_and_set_promise(function_object, *promise);
1046  }
1047  catch (...)
1048  {
1049  try
1050  {
1051  // store anything thrown in the promise
1052  promise->set_exception(std::current_exception());
1053  }
1054  catch (...)
1055  {
1056  // set_exception() may throw too. But ignore this on
1057  // the task.
1058  }
1059  }
1060  });
1061 
1062 #else
1063  // If no threading library is supported, just fall back onto C++11
1064  // facilities. The problem with this is that the standard does
1065  // not actually say what std::async should do. The first
1066  // argument to that function can be std::launch::async or
1067  // std::launch::deferred, or both. The *intent* of the standard's
1068  // authors was probably that if one sets it to
1069  // std::launch::async | std::launch::deferred,
1070  // that the task is run in a thread pool. But at least as of
1071  // 2021, GCC doesn't do that: It just runs it on a new thread.
1072  // If one chooses std::launch::deferred, it runs the task on
1073  // the same thread but only when one calls join() on the task's
1074  // std::future object. In the former case, this leads to
1075  // oversubscription, in the latter case to undersubscription of
1076  // resources. We choose oversubscription here.
1077  //
1078  // The issue illustrates why relying on external libraries
1079  // with task schedulers is the way to go.
1080  task_data = std::make_shared<TaskData>(
1081  std::async(std::launch::async | std::launch::deferred,
1082  function_object));
1083 #endif
1084  }
1085  else
1086  {
1087  // Only one thread allowed. So let the task run to completion
1088  // and just emplace a 'ready' future.
1089  //
1090  // The design of std::promise/std::future is unclear, but it
1091  // seems that the intent is to obtain the std::future before
1092  // we set the std::promise. So create the TaskData object at
1093  // the top and then run the task and set the returned
1094  // value. Since everything here happens sequentially, it
1095  // really doesn't matter in which order all of this is
1096  // happening.
1097  std::promise<RT> promise;
1098  task_data = std::make_shared<TaskData>(promise.get_future());
1099  try
1100  {
1101  internal::evaluate_and_set_promise(function_object, promise);
1102  }
1103  catch (...)
1104  {
1105  try
1106  {
1107  // store anything thrown in the promise
1108  promise.set_exception(std::current_exception());
1109  }
1110  catch (...)
1111  {
1112  // set_exception() may throw too. But ignore this on
1113  // the task.
1114  }
1115  }
1116  }
1117  }
1118 
1127  Task() = default;
1128 
1160  void
1161  join() const
1162  {
1163  // Make sure we actually have a task that we can wait for.
1165 
1166  task_data->wait();
1167  }
1168 
1181  bool
1182  joinable() const
1183  {
1184  return (task_data != nullptr);
1185  }
1186 
1187 
1239  {
1240  // Make sure we actually have a task that we can wait for.
1242 
1243  // Then return the promised object. If necessary, wait for the promise to
1244  // be set.
1245  return task_data->get();
1246  }
1247 
1248 
1258  "The current object is not associated with a task that "
1259  "can be joined. It may have been detached, or you "
1260  "may have already joined it in the past.");
1262  private:
1271  class TaskData
1272  {
1273  public:
1278  TaskData(std::future<RT> &&future) noexcept
1279  : future(std::move(future))
1280  , task_has_finished(false)
1281  {}
1282 
1287  TaskData(const TaskData &) = delete;
1288 
1293  TaskData(TaskData &&) = delete;
1294 
1299  TaskData &
1300  operator=(const TaskData &) = delete;
1301 
1306  TaskData &
1307  operator=(TaskData &&) = delete;
1308 
1316  ~TaskData() noexcept
1317  {
1318  // Explicitly wait for the results to be ready. This class stores
1319  // a std::future object, and we could just let the compiler generate
1320  // the destructor which would then call the destructor of std::future
1321  // which *may* block until the future is ready. As explained in
1322  // https://en.cppreference.com/w/cpp/thread/future/~future
1323  // this is only a *may*, not a *must*. (The standard does not
1324  // appear to say anything about it at all.) As a consequence,
1325  // let's be explicit about waiting.
1326  //
1327  // One of the corner cases we have to worry about is that if a task
1328  // ends by throwing an exception, then wait() will re-throw that
1329  // exception on the thread that calls it, the first time around
1330  // someone calls wait() (or the return_value() function of the
1331  // surrounding class). So if we get to this constructor and an exception
1332  // is thrown by wait(), then that means that the last Task object
1333  // referring to a task is going out of scope with nobody having
1334  // ever checked the return value of the task itself. In that case,
1335  // one could argue that they would also not have cared about whether
1336  // an exception is thrown, and that we should simply ignore the
1337  // exception. This is what we do here. It is also the simplest solution,
1338  // because we don't know what one should do with the exception to begin
1339  // with: destructors aren't allowed to throw exceptions, so we can't
1340  // just rethrow it here if one had been triggered.
1341  try
1342  {
1343  wait();
1344  }
1345  catch (...)
1346  {}
1347  }
1348 
1354  void
1356  {
1357  // If we have previously already moved the result, then we don't
1358  // need a lock and can just return.
1359  if (task_has_finished)
1360  return;
1361 
1362  // Else, we need to go under a lock and try again. A different thread
1363  // may have waited and finished the task since then, so we have to try
1364  // a second time. (This is Schmidt's double-checking pattern.)
1365  std::lock_guard<std::mutex> lock(mutex);
1366  if (task_has_finished)
1367  return;
1368  else
1369  {
1370 #ifdef DEAL_II_WITH_TBB
1371  // If we build on the TBB, then we can't just wait for the
1372  // std::future object to get ready. Apparently the TBB happily
1373  // enqueues a task into an arena and then just sits on it without
1374  // ever executing it unless someone expresses an interest in the
1375  // task. The way to avoid this is to add the task to a
1376  // tbb::task_group, and then here wait for the single task
1377  // associated with that task group.
1378  task_group.wait();
1379 #endif
1380 
1381  // Wait for the task to finish and then move its
1382  // result. (We could have made the set_from() function
1383  // that we call here wait for the future to be ready --
1384  // which happens implicitly when it calls future.get() --
1385  // but that would have required putting an explicit
1386  // future.wait() into the implementation of
1387  // internal::return_value<void>::set_from(), which is a
1388  // bit awkward: that class doesn't actually need to set
1389  // anything, and so it looks odd to have the explicit call
1390  // to future.wait() in the set_from() function. Avoid the
1391  // issue by just explicitly calling future.wait() here.)
1392  future.wait();
1393  returned_object.set_from(future);
1394 
1395  // Now we can safely set the flag and return.
1396  task_has_finished = true;
1397  }
1398  }
1399 
1400 
1401 
1404  {
1405  wait();
1406  return returned_object.get();
1407  }
1408 
1409  private:
1414  std::mutex mutex;
1415 
1420  std::future<RT> future;
1421 
1440  std::atomic<bool> task_has_finished;
1441 
1447 
1448 #ifdef DEAL_II_WITH_TBB
1452  tbb::task_group task_group;
1453 
1454  friend class Task<RT>;
1455 #endif
1456  };
1457 
1462  std::shared_ptr<TaskData> task_data;
1463  };
1464 
1465 
1466 
1486  template <typename RT>
1487  inline Task<RT>
1488  new_task(const std::function<RT()> &function)
1489  {
1490  return Task<RT>(function);
1491  }
1492 
1493 
1494 
1570  template <typename FunctionObjectType>
1571  DEAL_II_CXX20_REQUIRES((std::invocable<FunctionObjectType>))
1572  inline auto new_task(FunctionObjectType function_object)
1573  -> Task<decltype(function_object())>
1574  {
1575  using return_type = decltype(function_object());
1577  return new_task(std::function<return_type()>(function_object));
1578  }
1579 
1580 
1581 
1588  template <typename RT, typename... Args>
1589  inline Task<RT>
1590  new_task(RT (*fun_ptr)(Args...), std_cxx20::type_identity_t<Args>... args)
1591  {
1592  auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1593  return new_task(
1594  [dummy, fun_ptr]() -> RT { return std_cxx17::apply(fun_ptr, dummy); });
1595  }
1596 
1597 
1598 
1605  template <typename RT, typename C, typename... Args>
1606  inline Task<RT>
1607  new_task(RT (C::*fun_ptr)(Args...),
1610  {
1611  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1612  return new_task(std::function<RT()>(std::bind(
1613  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1614  }
1615 
1622  template <typename RT, typename C, typename... Args>
1623  inline Task<RT>
1624  new_task(RT (C::*fun_ptr)(Args...) const,
1627  {
1628  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1629  return new_task(std::function<RT()>(std::bind(
1630  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1631  }
1632 
1633 
1634  // ------------------------ TaskGroup -------------------------------------
1635 
1648  template <typename RT = void>
1650  {
1651  public:
1655  TaskGroup &
1657  {
1658  tasks.push_back(t);
1659  return *this;
1660  }
1661 
1662 
1670  std::size_t
1671  size() const
1672  {
1673  return tasks.size();
1674  }
1675 
1690  std::vector<RT>
1692  {
1693  std::vector<RT> results;
1694  results.reserve(size());
1695  for (auto &t : tasks)
1696  results.emplace_back(std::move(t.return_value()));
1697  return results;
1698  }
1699 
1700 
1707  void
1708  join_all() const
1709  {
1710  for (auto &t : tasks)
1711  t.join();
1712  }
1713 
1714  private:
1718  std::list<Task<RT>> tasks;
1719  };
1720 
1721 } // namespace Threads
1722 
1729 #endif
static void initialize_multithreading()
static unsigned int n_threads()
std::size_t size() const
std::list< Task< RT > > tasks
std::vector< RT > return_values()
TaskGroup & operator+=(const Task< RT > &t)
TaskData(std::future< RT > &&future) noexcept
TaskData(const TaskData &)=delete
std::atomic< bool > task_has_finished
internal::return_value< RT > returned_object
TaskData & operator=(const TaskData &)=delete
TaskData(TaskData &&)=delete
TaskData & operator=(TaskData &&)=delete
internal::return_value< RT >::reference_type get()
std::shared_ptr< TaskData > task_data
bool joinable() const
internal::return_value< RT >::reference_type return_value()
void join() const
Task()=default
Task(const std::function< RT()> &function_object)
std::list< Thread< RT > > threads
ThreadGroup & operator+=(const Thread< RT > &t)
internal::return_value< RT >::reference_type return_value()
bool valid() const
Thread(const std::function< RT()> &function)
std::shared_ptr< internal::ThreadDescriptor< RT > > thread_descriptor
bool operator==(const Thread &t) const
Thread()=default
Thread(const Thread< RT > &t)
#define DEAL_II_DEPRECATED
Definition: config.h:174
#define DEAL_II_NAMESPACE_OPEN
Definition: config.h:474
#define DEAL_II_DISABLE_EXTRA_DIAGNOSTICS
Definition: config.h:488
#define DEAL_II_CXX20_REQUIRES(condition)
Definition: config.h:162
#define DEAL_II_NAMESPACE_CLOSE
Definition: config.h:475
#define DEAL_II_ENABLE_EXTRA_DIAGNOSTICS
Definition: config.h:531
Point< 2 > second
Definition: grid_out.cc:4616
static ::ExceptionBase & ExcNoTask()
static ::ExceptionBase & ExcInternalError()
#define Assert(cond, exc)
Definition: exceptions.h:1586
#define DeclExceptionMsg(Exception, defaulttext)
Definition: exceptions.h:488
#define AssertThrow(cond, exc)
Definition: exceptions.h:1675
Thread< RT > new_thread(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)
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)
static const char T
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
void evaluate_and_set_promise(Function &function, std::promise< RT > &promise)
void call(const std::function< RT()> &function, internal::return_value< RT > &ret_val)
void handle_std_exception(const std::exception &exc)
Definition: mutex.h:32
VectorType::value_type * begin(VectorType &V)
VectorType::value_type * end(VectorType &V)
auto apply(F &&fn, Tuple &&t) -> decltype(apply_impl(std::forward< F >(fn), std::forward< Tuple >(t), std::make_index_sequence< std::tuple_size< typename std::remove_reference< Tuple >::type >::value >()))
Definition: tuple.h:36
typename type_identity< T >::type type_identity_t
Definition: type_traits.h:96
std::shared_ptr< return_value< RT > > ret_val
static void thread_entry_point(const std::function< RT()> &function, std::shared_ptr< return_value< RT >> ret_val)
void start(const std::function< RT()> &function)
static std::reference_wrapper< T > act(T &t)
void set_from(std::future< RT & > &v)
void set_from(std::future< void > &)
void set_from(std::future< RT > &v)
void advance(std::tuple< I1, I2 > &t, const unsigned int n)