Reference documentation for deal.II version Git 500a7ed831 2022-01-17 20:04:17 -0700
\(\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 - 2020 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 
22 # include <deal.II/base/exceptions.h>
26 
27 # include <atomic>
28 # include <condition_variable>
29 # include <functional>
30 # include <future>
31 # include <iterator>
32 # include <list>
33 # include <memory>
34 # include <mutex>
35 # include <thread>
36 # include <utility>
37 # include <vector>
38 
39 # ifdef DEAL_II_WITH_TBB
41 # include <tbb/task_group.h>
43 # endif
44 
46 
49 
50 
58 namespace Threads
59 {
78  class Mutex : public std::mutex
79  {
80  public:
84  Mutex() = default;
85 
90  Mutex(const Mutex &)
91  : std::mutex()
92  {}
93 
98  Mutex &
99  operator=(const Mutex &)
100  {
101  return *this;
102  }
103  };
104 } // namespace Threads
105 
106 
107 namespace Threads
108 {
123  template <typename ForwardIterator>
124  std::vector<std::pair<ForwardIterator, ForwardIterator>>
125  split_range(const ForwardIterator &begin,
126  const ForwardIterator &end,
127  const unsigned int n_intervals);
128 
137  std::vector<std::pair<unsigned int, unsigned int>>
138  split_interval(const unsigned int begin,
139  const unsigned int end,
140  const unsigned int n_intervals);
141 
151  namespace internal
152  {
168  [[noreturn]] void
169  handle_std_exception(const std::exception &exc);
170 
178  [[noreturn]] void
180  } // namespace internal
181 
186 } // namespace Threads
187 
188 /* ----------- implementation of functions in namespace Threads ---------- */
189 # ifndef DOXYGEN
190 namespace Threads
191 {
192  template <typename ForwardIterator>
193  std::vector<std::pair<ForwardIterator, ForwardIterator>>
194  split_range(const ForwardIterator &begin,
195  const ForwardIterator &end,
196  const unsigned int n_intervals)
197  {
198  using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
199 
200  // in non-multithreaded mode, we often have the case that this
201  // function is called with n_intervals==1, so have a shortcut here
202  // to handle that case efficiently
203 
204  if (n_intervals == 1)
205  return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
206 
207  // if more than one interval requested, do the full work
208  const unsigned int n_elements = std::distance(begin, end);
209  const unsigned int n_elements_per_interval = n_elements / n_intervals;
210  const unsigned int residual = n_elements % n_intervals;
211 
212  std::vector<IteratorPair> return_values(n_intervals);
213 
214  return_values[0].first = begin;
215  for (unsigned int i = 0; i < n_intervals; ++i)
216  {
217  if (i != n_intervals - 1)
218  {
219  return_values[i].second = return_values[i].first;
220  // note: the cast is performed to avoid a warning of gcc
221  // that in the library `dist>=0' is checked (dist has a
222  // template type, which here is unsigned if no cast is
223  // performed)
224  std::advance(return_values[i].second,
225  static_cast<signed int>(n_elements_per_interval));
226  // distribute residual in division equally among the first
227  // few subintervals
228  if (i < residual)
229  ++return_values[i].second;
230 
231  return_values[i + 1].first = return_values[i].second;
232  }
233  else
234  return_values[i].second = end;
235  }
236  return return_values;
237  }
238 } // namespace Threads
239 
240 # endif // DOXYGEN
241 
242 namespace Threads
243 {
244  namespace internal
245  {
264  template <typename RT>
266  {
267  private:
268  RT value;
269 
270  public:
271  using reference_type = RT &;
272 
273  inline return_value()
274  : value()
275  {}
276 
277  inline reference_type
278  get()
279  {
280  return value;
281  }
282 
283  inline void
284  set(RT &&v)
285  {
286  value = std::move(v);
287  }
288 
289  inline void
290  set_from(std::future<RT> &v)
291  {
292  value = std::move(v.get());
293  }
294  };
295 
296 
316  template <typename RT>
317  struct return_value<RT &>
318  {
319  private:
320  RT *value;
321 
322  public:
323  using reference_type = RT &;
324 
325  inline return_value()
326  : value(nullptr)
327  {}
328 
329  inline reference_type
330  get() const
331  {
332  return *value;
333  }
334 
335  inline void
336  set(RT &v)
337  {
338  value = &v;
339  }
340 
341  inline void
342  set_from(std::future<RT &> &v)
343  {
344  value = &v.get();
345  }
346  };
347 
348 
367  template <>
368  struct return_value<void>
369  {
370  using reference_type = void;
371 
372  static inline void
373  get()
374  {}
375 
376 
377  inline void
378  set_from(std::future<void> &)
379  {}
380  };
381  } // namespace internal
382 
383 
384 
385  namespace internal
386  {
387  template <typename RT>
388  inline void
389  call(const std::function<RT()> & function,
391  {
392  ret_val.set(function());
393  }
394 
395 
396  inline void
397  call(const std::function<void()> &function, internal::return_value<void> &)
398  {
399  function();
400  }
401  } // namespace internal
402 
403 
404 
405  namespace internal
406  {
417  template <typename RT>
419  {
423  std::thread thread;
424 
433  std::shared_ptr<return_value<RT>> ret_val;
434 
470  std::atomic<bool> thread_is_active;
471 
476 
481  : thread_is_active(false)
482  {}
483 
485  {
486  if (!thread_is_active)
487  return;
488  thread.detach();
489  thread_is_active = false;
490  }
491 
496  void
497  start(const std::function<RT()> &function)
498  {
499  thread_is_active = true;
500  ret_val = std::make_shared<return_value<RT>>();
501  thread = std::thread(thread_entry_point, function, ret_val);
502  }
503 
504 
508  void
510  {
511  // see if the thread hasn't been joined yet. if it has, then
512  // join() is a no-op. use schmidt's double-checking strategy
513  // to use the mutex only when necessary
514  if (thread_is_active == false)
515  return;
516 
517  std::lock_guard<std::mutex> lock(thread_is_active_mutex);
518  if (thread_is_active == true)
519  {
520  Assert(thread.joinable(), ExcInternalError());
521  thread.join();
522  thread_is_active = false;
523  }
524  }
525 
526  private:
530  static void
531  thread_entry_point(const std::function<RT()> & function,
532  std::shared_ptr<return_value<RT>> ret_val)
533  {
534  // call the function in question. since an exception that is
535  // thrown from one of the called functions will not propagate
536  // to the main thread, it will kill the program if not treated
537  // here before we return to the operating system's thread
538  // library
539  try
540  {
541  call(function, *ret_val);
542  }
543  catch (const std::exception &exc)
544  {
546  }
547  catch (...)
548  {
550  }
551  }
552  };
553  } // namespace internal
554 
555 
582  template <typename RT = void>
583  class Thread
584  {
585  public:
590  Thread(const std::function<RT()> &function)
591  : thread_descriptor(new internal::ThreadDescriptor<RT>())
592  {
593  // in a second step, start the thread.
594  thread_descriptor->start(function);
595  }
596 
603  Thread() = default;
604 
609  Thread(const Thread<RT> &t)
610  : thread_descriptor(t.thread_descriptor)
611  {}
612 
618  void
619  join() const
620  {
621  if (thread_descriptor)
622  thread_descriptor->join();
623  }
624 
670  {
671  join();
672  return thread_descriptor->ret_val->get();
673  }
674 
679  bool
680  valid() const
681  {
682  return static_cast<bool>(thread_descriptor);
683  }
684 
685 
691  bool
692  operator==(const Thread &t) const
693  {
694  return thread_descriptor == t.thread_descriptor;
695  }
696 
697  private:
703  std::shared_ptr<internal::ThreadDescriptor<RT>> thread_descriptor;
704  };
705 
706 
707  namespace internal
708  {
716  template <typename T>
718  {
719  static T
720  act(T &t)
721  {
722  return t;
723  }
724  };
725 
726 
727 
735  template <typename T>
736  struct maybe_make_ref<T &>
737  {
738  static std::reference_wrapper<T>
739  act(T &t)
740  {
741  return std::ref(t);
742  }
743  };
744  } // namespace internal
745 
746 
747 
748  // ----------- thread starters for functions not taking any parameters
749 
760  template <typename RT>
762  new_thread(const std::function<RT()> &function)
763  {
764  return Thread<RT>(function);
765  }
766 
767 
768 
835  template <typename FunctionObjectType>
836  DEAL_II_DEPRECATED inline auto
837  new_thread(FunctionObjectType function_object)
839  {
840  using return_type = decltype(function_object());
841  return Thread<return_type>(std::function<return_type()>(function_object));
842  }
843 
844 
845 
854  template <typename RT, typename... Args>
856  new_thread(RT (*fun_ptr)(Args...), typename identity<Args>::type... args)
857  {
858  auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
859  return new_thread(
860  [dummy, fun_ptr]() -> RT { return std_cxx17::apply(fun_ptr, dummy); });
861  }
862 
863 
864 
872  template <typename RT, typename C, typename... Args>
874  new_thread(RT (C::*fun_ptr)(Args...),
875  typename identity<C>::type &c,
876  typename identity<Args>::type... args)
877  {
878  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
879  return new_thread(std::function<RT()>(std::bind(
880  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
881  }
882 
890  template <typename RT, typename C, typename... Args>
892  new_thread(RT (C::*fun_ptr)(Args...) const,
893  typename identity<const C>::type &c,
894  typename identity<Args>::type... args)
895  {
896  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
897  return new_thread(std::function<RT()>(std::bind(
898  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
899  }
900 
901  // ------------------------ ThreadGroup -------------------------------------
902 
912  template <typename RT = void>
914  {
915  public:
919  ThreadGroup &
921  {
922  threads.push_back(t);
923  return *this;
924  }
925 
932  void
933  join_all() const
934  {
935  for (auto &t : threads)
936  t.join();
937  }
938 
939  private:
943  std::list<Thread<RT>> threads;
944  };
945 
946 
947  namespace internal
948  {
952  template <typename RT, typename Function>
953  void
954  evaluate_and_set_promise(Function &function, std::promise<RT> &promise)
955  {
956  promise.set_value(function());
957  }
958 
959 
967  template <typename Function>
968  void
969  evaluate_and_set_promise(Function &function, std::promise<void> &promise)
970  {
971  function();
972  promise.set_value();
973  }
974  } // namespace internal
975 
976 
977 
1004  template <typename RT = void>
1005  class Task
1006  {
1007  public:
1019  Task(const std::function<RT()> &function_object)
1020  {
1021  if (MultithreadInfo::n_threads() > 1)
1022  {
1023 # ifdef DEAL_II_WITH_TBB
1024  // Create a promise object and from it extract a future that
1025  // we can use to refer to the outcome of the task. For reasons
1026  // explained below, we can't just create a std::promise object,
1027  // but have to make do with a pointer to such an object.
1028  std::unique_ptr<std::promise<RT>> promise =
1029  std::make_unique<std::promise<RT>>();
1030  task_data =
1031  std::make_shared<TaskData>(std::move(promise->get_future()));
1032 
1033  // Then start the task, using a task_group object (for just this one
1034  // task) that is associated with the TaskData object. Note that we
1035  // have to *copy* the function object being executed so that it is
1036  // guaranteed to live on the called thread as well -- the copying is
1037  // facilitated by capturing the 'function_object' variable by value.
1038  //
1039  // We also have to *move* the promise object into the new task's
1040  // memory space because promises can not be copied and we can't refer
1041  // to it by reference because it's a local variable of the current
1042  // (surrounding) function that may go out of scope before the promise
1043  // is ultimately set. This leads to a conundrum: if we had just
1044  // declared 'promise' as an object of type std::promise, then we could
1045  // capture it in the lambda function via
1046  // [..., promise=std::move(promise)]() {...}
1047  // and set the promise in the body of the lambda. But setting a
1048  // promise is a non-const operation on the promise, and so we would
1049  // actually have to declare the lambda function as 'mutable' because
1050  // by default, lambda captures are 'const'. That is, we would have
1051  // to write
1052  // [..., promise=std::move(promise)]() mutable {...}
1053  // But this leads to other problems: It turns out that the
1054  // tbb::task_group::run() function cannot take mutable lambdas as
1055  // argument :-(
1056  //
1057  // We work around this issue by not declaring the 'promise' variable
1058  // as an object of type std::promise, but as a pointer to such an
1059  // object. This pointer we can move, and the *pointer* itself can
1060  // be 'const' (meaning we can leave the lambda as non-mutable)
1061  // even though we modify the object *pointed to*. One would think
1062  // that a std::unique_ptr would be the right choice for this, but
1063  // that's not true: the resulting lambda function can then be
1064  // non-mutable, but the lambda function object is not copyable
1065  // and at least some TBB variants require that as well. So
1066  // instead we move the std::unique_ptr used above into a
1067  // std::shared_ptr to be stored within the lambda function object.
1068  task_data->task_group.run(
1069  [function_object,
1070  promise =
1071  std::shared_ptr<std::promise<RT>>(std::move(promise))]() {
1072  try
1073  {
1074  internal::evaluate_and_set_promise(function_object, *promise);
1075  }
1076  catch (...)
1077  {
1078  try
1079  {
1080  // store anything thrown in the promise
1081  promise->set_exception(std::current_exception());
1082  }
1083  catch (...)
1084  {
1085  // set_exception() may throw too. But ignore this on
1086  // the task.
1087  }
1088  }
1089  });
1090 
1091 # else
1092  // If no threading library is supported, just fall back onto C++11
1093  // facilities. The problem with this is that the standard does
1094  // not actually say what std::async should do. The first
1095  // argument to that function can be std::launch::async or
1096  // std::launch::deferred, or both. The *intent* of the standard's
1097  // authors was probably that if one sets it to
1098  // std::launch::async | std::launch::deferred,
1099  // that the task is run in a thread pool. But at least as of
1100  // 2021, GCC doesn't do that: It just runs it on a new thread.
1101  // If one chooses std::launch::deferred, it runs the task on
1102  // the same thread but only when one calls join() on the task's
1103  // std::future object. In the former case, this leads to
1104  // oversubscription, in the latter case to undersubscription of
1105  // resources. We choose oversubscription here.
1106  //
1107  // The issue illustrates why relying on external libraries
1108  // with task schedulers is the way to go.
1109  task_data = std::make_shared<TaskData>(
1110  std::async(std::launch::async | std::launch::deferred,
1111  function_object));
1112 # endif
1113  }
1114  else
1115  {
1116  // Only one thread allowed. So let the task run to completion
1117  // and just emplace a 'ready' future.
1118  //
1119  // The design of std::promise/std::future is unclear, but it
1120  // seems that the intent is to obtain the std::future before
1121  // we set the std::promise. So create the TaskData object at
1122  // the top and then run the task and set the returned
1123  // value. Since everything here happens sequentially, it
1124  // really doesn't matter in which order all of this is
1125  // happening.
1126  std::promise<RT> promise;
1127  task_data = std::make_shared<TaskData>(promise.get_future());
1128  try
1129  {
1130  internal::evaluate_and_set_promise(function_object, promise);
1131  }
1132  catch (...)
1133  {
1134  try
1135  {
1136  // store anything thrown in the promise
1137  promise.set_exception(std::current_exception());
1138  }
1139  catch (...)
1140  {
1141  // set_exception() may throw too. But ignore this on
1142  // the task.
1143  }
1144  }
1145  }
1146  }
1147 
1156  Task() = default;
1157 
1189  void
1190  join() const
1191  {
1192  // Make sure we actually have a task that we can wait for.
1193  AssertThrow(joinable(), ExcNoTask());
1194 
1195  task_data->wait();
1196  }
1197 
1210  bool
1211  joinable() const
1212  {
1213  return (task_data != nullptr);
1214  }
1215 
1216 
1268  {
1269  // Make sure we actually have a task that we can wait for.
1270  AssertThrow(joinable(), ExcNoTask());
1271 
1272  // Then return the promised object. If necessary, wait for the promise to
1273  // be set.
1274  return task_data->get();
1275  }
1276 
1277 
1286  DeclExceptionMsg(ExcNoTask,
1287  "The current object is not associated with a task that "
1288  "can be joined. It may have been detached, or you "
1289  "may have already joined it in the past.");
1291  private:
1300  class TaskData
1301  {
1302  public:
1307  TaskData(std::future<RT> &&future) noexcept
1308  : future(std::move(future))
1309  , task_has_finished(false)
1310  {}
1311 
1316  TaskData(const TaskData &) = delete;
1317 
1322  TaskData(TaskData &&) = delete;
1323 
1328  TaskData &
1329  operator=(const TaskData &) = delete;
1330 
1335  TaskData &
1336  operator=(TaskData &&) = delete;
1337 
1345  ~TaskData() noexcept
1346  {
1347  // Explicitly wait for the results to be ready. This class stores
1348  // a std::future object, and we could just let the compiler generate
1349  // the destructor which would then call the destructor of std::future
1350  // which *may* block until the future is ready. As explained in
1351  // https://en.cppreference.com/w/cpp/thread/future/~future
1352  // this is only a *may*, not a *must*. (The standard does not
1353  // appear to say anything about it at all.) As a consequence,
1354  // let's be explicit about waiting.
1355  //
1356  // One of the corner cases we have to worry about is that if a task
1357  // ends by throwing an exception, then wait() will re-throw that
1358  // exception on the thread that calls it, the first time around
1359  // someone calls wait() (or the return_value() function of the
1360  // surrounding class). So if we get to this constructor and an exception
1361  // is thrown by wait(), then that means that the last Task object
1362  // referring to a task is going out of scope with nobody having
1363  // ever checked the return value of the task itself. In that case,
1364  // one could argue that they would also not have cared about whether
1365  // an exception is thrown, and that we should simply ignore the
1366  // exception. This is what we do here. It is also the simplest solution,
1367  // because we don't know what one should do with the exception to begin
1368  // with: destructors aren't allowed to throw exceptions, so we can't
1369  // just rethrow it here if one had been triggered.
1370  try
1371  {
1372  wait();
1373  }
1374  catch (...)
1375  {}
1376  }
1377 
1383  void
1385  {
1386  // If we have previously already moved the result, then we don't
1387  // need a lock and can just return.
1388  if (task_has_finished)
1389  return;
1390 
1391  // Else, we need to go under a lock and try again. A different thread
1392  // may have waited and finished the task since then, so we have to try
1393  // a second time. (This is Schmidt's double-checking pattern.)
1394  std::lock_guard<std::mutex> lock(mutex);
1395  if (task_has_finished)
1396  return;
1397  else
1398  {
1399 # ifdef DEAL_II_WITH_TBB
1400  // If we build on the TBB, then we can't just wait for the
1401  // std::future object to get ready. Apparently the TBB happily
1402  // enqueues a task into an arena and then just sits on it without
1403  // ever executing it unless someone expresses an interest in the
1404  // task. The way to avoid this is to add the task to a
1405  // tbb::task_group, and then here wait for the single task
1406  // associated with that task group.
1407  task_group.wait();
1408 # endif
1409 
1410  // Wait for the task to finish and then move its
1411  // result. (We could have made the set_from() function
1412  // that we call here wait for the future to be ready --
1413  // which happens implicitly when it calls future.get() --
1414  // but that would have required putting an explicit
1415  // future.wait() into the implementation of
1416  // internal::return_value<void>::set_from(), which is a
1417  // bit awkward: that class doesn't actually need to set
1418  // anything, and so it looks odd to have the explicit call
1419  // to future.wait() in the set_from() function. Avoid the
1420  // issue by just explicitly calling future.wait() here.)
1421  future.wait();
1422  returned_object.set_from(future);
1423 
1424  // Now we can safely set the flag and return.
1425  task_has_finished = true;
1426  }
1427  }
1428 
1429 
1430 
1432  get()
1433  {
1434  wait();
1435  return returned_object.get();
1436  }
1437 
1438  private:
1443  std::mutex mutex;
1444 
1449  std::future<RT> future;
1450 
1469  std::atomic<bool> task_has_finished;
1470 
1476 
1477 # ifdef DEAL_II_WITH_TBB
1478 
1481  tbb::task_group task_group;
1482 
1483  friend class Task<RT>;
1484 # endif
1485  };
1486 
1491  std::shared_ptr<TaskData> task_data;
1492  };
1493 
1494 
1495 
1515  template <typename RT>
1516  inline Task<RT>
1517  new_task(const std::function<RT()> &function)
1518  {
1519  return Task<RT>(function);
1520  }
1521 
1522 
1523 
1599  template <typename FunctionObjectType>
1600  inline auto
1601  new_task(FunctionObjectType function_object)
1603  {
1604  using return_type = decltype(function_object());
1606  return new_task(std::function<return_type()>(function_object));
1607  }
1608 
1609 
1610 
1617  template <typename RT, typename... Args>
1618  inline Task<RT>
1619  new_task(RT (*fun_ptr)(Args...), typename identity<Args>::type... args)
1620  {
1621  auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1622  return new_task(
1623  [dummy, fun_ptr]() -> RT { return std_cxx17::apply(fun_ptr, dummy); });
1624  }
1625 
1626 
1627 
1634  template <typename RT, typename C, typename... Args>
1635  inline Task<RT>
1636  new_task(RT (C::*fun_ptr)(Args...),
1637  typename identity<C>::type &c,
1638  typename identity<Args>::type... args)
1639  {
1640  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1641  return new_task(std::function<RT()>(std::bind(
1642  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1643  }
1644 
1651  template <typename RT, typename C, typename... Args>
1652  inline Task<RT>
1653  new_task(RT (C::*fun_ptr)(Args...) const,
1654  typename identity<const C>::type &c,
1655  typename identity<Args>::type... args)
1656  {
1657  // NOLINTNEXTLINE(modernize-avoid-bind) silence clang-tidy
1658  return new_task(std::function<RT()>(std::bind(
1659  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1660  }
1661 
1662 
1663  // ------------------------ TaskGroup -------------------------------------
1664 
1677  template <typename RT = void>
1679  {
1680  public:
1684  TaskGroup &
1686  {
1687  tasks.push_back(t);
1688  return *this;
1689  }
1690 
1691 
1699  std::size_t
1700  size() const
1701  {
1702  return tasks.size();
1703  }
1704 
1705 
1712  void
1713  join_all() const
1714  {
1715  for (auto &t : tasks)
1716  t.join();
1717  }
1718 
1719  private:
1723  std::list<Task<RT>> tasks;
1724  };
1725 
1726 } // namespace Threads
1727 
1733 //---------------------------------------------------------------------------
1735 // end of #ifndef dealii_thread_management_h
1736 #endif
1737 //---------------------------------------------------------------------------
void evaluate_and_set_promise(Function &function, std::promise< void > &promise)
std::shared_ptr< internal::ThreadDescriptor< RT > > thread_descriptor
bool valid() const
Task< RT > new_task(const std::function< RT()> &function)
Thread(const Thread< RT > &t)
Mutex(const Mutex &)
void evaluate_and_set_promise(Function &function, std::promise< RT > &promise)
std::list< Task< RT > > tasks
internal::return_value< RT >::reference_type return_value()
void join() const
void call(const std::function< void()> &function, internal::return_value< void > &)
std::size_t size() const
std::shared_ptr< return_value< RT > > ret_val
STL namespace.
#define AssertThrow(cond, exc)
Definition: exceptions.h:1571
Point< 2 > second
Definition: grid_out.cc:4615
std::shared_ptr< TaskData > task_data
TaskData(std::future< RT > &&future) noexcept
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
std::vector< std::pair< unsigned int, unsigned int > > split_interval(const unsigned int begin, const unsigned int end, const unsigned int n_intervals)
Task(const std::function< RT()> &function_object)
#define DEAL_II_DISABLE_EXTRA_DIAGNOSTICS
Definition: config.h:420
Mutex & operator=(const Mutex &)
static const char T
void set_from(std::future< void > &)
void set_from(std::future< RT &> &v)
#define Assert(cond, exc)
Definition: exceptions.h:1461
static void initialize_multithreading()
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
#define DeclExceptionMsg(Exception, defaulttext)
Definition: exceptions.h:487
Mutex()=default
std::atomic< bool > task_has_finished
#define DEAL_II_NAMESPACE_CLOSE
Definition: config.h:407
VectorType::value_type * end(VectorType &V)
void set_from(std::future< RT > &v)
bool joinable() const
Thread(const std::function< RT()> &function)
static std::reference_wrapper< T > act(T &t)
internal::return_value< RT >::reference_type return_value()
void advance(std::tuple< I1, I2 > &t, const unsigned int n)
bool operator==(const Thread &t) const
#define DEAL_II_ENABLE_EXTRA_DIAGNOSTICS
Definition: config.h:458
#define DEAL_II_NAMESPACE_OPEN
Definition: config.h:406
VectorType::value_type * begin(VectorType &V)
TaskGroup & operator+=(const Task< RT > &t)
void start(const std::function< RT()> &function)
ThreadGroup & operator+=(const Thread< RT > &t)
Thread< RT > new_thread(const std::function< RT()> &function)
static void thread_entry_point(const std::function< RT()> &function, std::shared_ptr< return_value< RT >> ret_val)
static unsigned int n_threads()
void handle_std_exception(const std::exception &exc)
internal::return_value< RT > returned_object
#define DEAL_II_DEPRECATED
Definition: config.h:163
std::vector< std::pair< ForwardIterator, ForwardIterator > > split_range(const ForwardIterator &begin, const ForwardIterator &end, const unsigned int n_intervals)
std::list< Thread< RT > > threads
static ::ExceptionBase & ExcInternalError()