Reference documentation for deal.II version 9.1.1
\(\newcommand{\dealcoloneq}{\mathrel{\vcenter{:}}=}\)
thread_management.h
1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2000 - 2019 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>
23 # include <deal.II/base/multithread_info.h>
24 # include <deal.II/base/template_constraints.h>
25 
26 # include <condition_variable>
27 # include <functional>
28 # include <iterator>
29 # include <list>
30 # include <memory>
31 # include <mutex>
32 # include <tuple>
33 # include <utility>
34 # include <vector>
35 DEAL_II_DISABLE_EXTRA_DIAGNOSTICS
36 # ifdef DEAL_II_WITH_THREADS
37 # include <thread>
38 # ifdef DEAL_II_USE_MT_POSIX
39 # include <pthread.h>
40 # endif
41 # include <tbb/task.h>
42 # include <tbb/tbb_stddef.h>
43 # endif
44 DEAL_II_ENABLE_EXTRA_DIAGNOSTICS
45 
46 
47 
48 DEAL_II_NAMESPACE_OPEN
49 
52 
53 
61 namespace Threads
62 {
88  class Mutex : public std::mutex
89  {
90  public:
111  using ScopedLock DEAL_II_DEPRECATED = std::lock_guard<std::mutex>;
112 
116  Mutex() = default;
117 
122  Mutex(const Mutex &)
123  : std::mutex()
124  {}
125 
126 
131  Mutex &
132  operator=(const Mutex &)
133  {
134  return *this;
135  }
136 
137 
144  DEAL_II_DEPRECATED
145  inline void
147  {
148  this->lock();
149  }
150 
157  DEAL_II_DEPRECATED
158  inline void
160  {
161  this->unlock();
162  }
163  };
164 
165 
174  class DEAL_II_DEPRECATED ConditionVariable
175  {
176  public:
181  inline void
183  {
184  condition_variable.notify_one();
185  }
186 
191  inline void
193  {
194  condition_variable.notify_all();
195  }
196 
206  inline void
207  wait(Mutex &mutex)
208  {
209  std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
210  condition_variable.wait(lock);
211  }
212 
213  private:
217  std::condition_variable condition_variable;
218  };
219 
220 # ifdef DEAL_II_USE_MT_POSIX
221 
239  class DEAL_II_DEPRECATED PosixThreadBarrier
240  {
241  public:
245  PosixThreadBarrier(const unsigned int count,
246  const char * name = nullptr,
247  void * arg = nullptr);
248 
253 
260  int
261  wait();
262 
263  private:
268 # ifndef DEAL_II_USE_MT_POSIX_NO_BARRIERS
269  pthread_barrier_t barrier;
270 # else
271  unsigned int count;
272 # endif
273  };
274 
275 
283  using Barrier DEAL_II_DEPRECATED = PosixThreadBarrier;
284 # endif
285 } // namespace Threads
286 
287 
288 namespace Threads
289 {
328  DEAL_II_DEPRECATED
329  unsigned int
331 
345  DEAL_II_DEPRECATED
346  unsigned int
347  this_thread_id();
348 
363  template <typename ForwardIterator>
364  std::vector<std::pair<ForwardIterator, ForwardIterator>>
365  split_range(const ForwardIterator &begin,
366  const ForwardIterator &end,
367  const unsigned int n_intervals);
368 
377  std::vector<std::pair<unsigned int, unsigned int>>
378  split_interval(const unsigned int begin,
379  const unsigned int end,
380  const unsigned int n_intervals);
381 
393  namespace internal
394  {
410  [[noreturn]] void
411  handle_std_exception(const std::exception &exc);
412 
420  [[noreturn]] void
421  handle_unknown_exception();
422 
430  void
431  register_thread();
432 
440  void
441  deregister_thread();
442  } // namespace internal
443 
448 } // namespace Threads
449 
450 /* ----------- implementation of functions in namespace Threads ---------- */
451 # ifndef DOXYGEN
452 namespace Threads
453 {
454  template <typename ForwardIterator>
455  std::vector<std::pair<ForwardIterator, ForwardIterator>>
456  split_range(const ForwardIterator &begin,
457  const ForwardIterator &end,
458  const unsigned int n_intervals)
459  {
460  using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
461 
462  // in non-multithreaded mode, we often have the case that this
463  // function is called with n_intervals==1, so have a shortcut here
464  // to handle that case efficiently
465 
466  if (n_intervals == 1)
467  return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
468 
469  // if more than one interval requested, do the full work
470  const unsigned int n_elements = std::distance(begin, end);
471  const unsigned int n_elements_per_interval = n_elements / n_intervals;
472  const unsigned int residual = n_elements % n_intervals;
473 
474  std::vector<IteratorPair> return_values(n_intervals);
475 
476  return_values[0].first = begin;
477  for (unsigned int i = 0; i < n_intervals; ++i)
478  {
479  if (i != n_intervals - 1)
480  {
481  return_values[i].second = return_values[i].first;
482  // note: the cast is performed to avoid a warning of gcc
483  // that in the library `dist>=0' is checked (dist has a
484  // template type, which here is unsigned if no cast is
485  // performed)
486  std::advance(return_values[i].second,
487  static_cast<signed int>(n_elements_per_interval));
488  // distribute residual in division equally among the first
489  // few subintervals
490  if (i < residual)
491  ++return_values[i].second;
492 
493  return_values[i + 1].first = return_values[i].second;
494  }
495  else
496  return_values[i].second = end;
497  }
498  return return_values;
499  }
500 } // namespace Threads
501 
502 # endif // DOXYGEN
503 
504 namespace Threads
505 {
506  namespace internal
507  {
516  template <typename RT>
517  struct return_value
518  {
519  private:
520  RT value;
521 
522  public:
523  using reference_type = RT &;
524 
525  inline return_value()
526  : value()
527  {}
528 
529  inline reference_type
530  get()
531  {
532  return value;
533  }
534 
535  inline void
536  set(RT &&v)
537  {
538  value = std::move(v);
539  }
540  };
541 
542 
552  template <typename RT>
553  struct return_value<RT &>
554  {
555  private:
556  RT *value;
557 
558  public:
559  using reference_type = RT &;
560 
561  inline return_value()
562  : value(nullptr)
563  {}
564 
565  inline reference_type
566  get() const
567  {
568  return *value;
569  }
570 
571  inline void
572  set(RT &v)
573  {
574  value = &v;
575  }
576  };
577 
578 
587  template <>
588  struct return_value<void>
589  {
590  using reference_type = void;
591 
592  static inline void
593  get()
594  {}
595  };
596  } // namespace internal
597 
598 
599 
600  namespace internal
601  {
602  template <typename RT>
603  inline void
604  call(const std::function<RT()> & function,
605  internal::return_value<RT> &ret_val)
606  {
607  ret_val.set(function());
608  }
609 
610 
611  inline void
612  call(const std::function<void()> &function, internal::return_value<void> &)
613  {
614  function();
615  }
616  } // namespace internal
617 
618 
619 
620  namespace internal
621  {
622 # ifdef DEAL_II_WITH_THREADS
623 
634  template <typename RT>
636  {
640  std::thread thread;
641 
650  std::shared_ptr<return_value<RT>> ret_val;
651 
684 
689 
694  : thread_is_active(false)
695  {}
696 
698  {
699  if (!thread_is_active)
700  return;
701  thread.detach();
702  thread_is_active = false;
703  }
704 
709  void
710  start(const std::function<RT()> &function)
711  {
712  thread_is_active = true;
713  ret_val = std::make_shared<return_value<RT>>();
714  thread = std::thread(thread_entry_point, function, ret_val);
715  }
716 
717 
721  void
723  {
724  // see if the thread hasn't been joined yet. if it has, then
725  // join() is a no-op. use schmidt's double-checking strategy
726  // to use the mutex only when necessary
727  if (thread_is_active == false)
728  return;
729 
730  Mutex::ScopedLock lock(thread_is_active_mutex);
731  if (thread_is_active == true)
732  {
733  Assert(thread.joinable(), ExcInternalError());
734  thread.join();
735  thread_is_active = false;
736  }
737  }
738 
739  private:
743  static void
744  thread_entry_point(const std::function<RT()> & function,
745  std::shared_ptr<return_value<RT>> ret_val)
746  {
747  // call the function in question. since an exception that is
748  // thrown from one of the called functions will not propagate
749  // to the main thread, it will kill the program if not treated
750  // here before we return to the operating system's thread
751  // library
752  internal::register_thread();
753  try
754  {
755  call(function, *ret_val);
756  }
757  catch (const std::exception &exc)
758  {
759  internal::handle_std_exception(exc);
760  }
761  catch (...)
762  {
763  internal::handle_unknown_exception();
764  }
765  internal::deregister_thread();
766  }
767  };
768 
769 # else
770 
778  template <typename RT>
779  struct ThreadDescriptor
780  {
785  std::shared_ptr<return_value<RT>> ret_val;
786 
791  void
792  start(const std::function<RT()> &function)
793  {
794  ret_val = std::make_shared<return_value<RT>>();
795  call(function, *ret_val);
796  }
797 
801  void
802  join()
803  {}
804  };
805 
806 # endif
807  } // namespace internal
808 
809 
832  template <typename RT = void>
833  class Thread
834  {
835  public:
839  Thread(const std::function<RT()> &function)
840  : thread_descriptor(new internal::ThreadDescriptor<RT>())
841  {
842  // in a second step, start the thread.
843  thread_descriptor->start(function);
844  }
845 
851  Thread() = default;
852 
856  Thread(const Thread<RT> &t)
857  : thread_descriptor(t.thread_descriptor)
858  {}
859 
865  void
866  join() const
867  {
868  if (thread_descriptor)
869  thread_descriptor->join();
870  }
871 
915  typename internal::return_value<RT>::reference_type
917  {
918  join();
919  return thread_descriptor->ret_val->get();
920  }
921 
926  bool
927  valid() const
928  {
929  return static_cast<bool>(thread_descriptor);
930  }
931 
932 
938  bool
939  operator==(const Thread &t) const
940  {
941  return thread_descriptor == t.thread_descriptor;
942  }
943 
944  private:
950  std::shared_ptr<internal::ThreadDescriptor<RT>> thread_descriptor;
951  };
952 
953 
954  namespace internal
955  {
963  template <typename T>
965  {
966  static T
967  act(T &t)
968  {
969  return t;
970  }
971  };
972 
973 
974 
982  template <typename T>
983  struct maybe_make_ref<T &>
984  {
985  static std::reference_wrapper<T>
986  act(T &t)
987  {
988  return std::ref(t);
989  }
990  };
991  } // namespace internal
992 
993 
994 
995  // ----------- thread starters for functions not taking any parameters
996 
1005  template <typename RT>
1006  inline Thread<RT>
1007  new_thread(const std::function<RT()> &function)
1008  {
1009  return Thread<RT>(function);
1010  }
1011 
1012 
1013 
1078  template <typename FunctionObjectType>
1079  inline auto
1080  new_thread(FunctionObjectType function_object)
1082  {
1083  using return_type = decltype(function_object());
1084  return Thread<return_type>(std::function<return_type()>(function_object));
1085  }
1086 
1087 
1088 
1095  template <typename RT, typename... Args>
1096  inline Thread<RT>
1097  new_thread(RT (*fun_ptr)(Args...), typename identity<Args>::type... args)
1098  {
1099  return new_thread(std::function<RT()>(
1100  std::bind(fun_ptr, internal::maybe_make_ref<Args>::act(args)...)));
1101  }
1102 
1103 
1104 
1110  template <typename RT, typename C, typename... Args>
1111  inline Thread<RT>
1112  new_thread(RT (C::*fun_ptr)(Args...),
1113  typename identity<C>::type &c,
1114  typename identity<Args>::type... args)
1115  {
1116  return new_thread(std::function<RT()>(std::bind(
1117  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1118  }
1119 
1120 # ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG
1121 
1126  template <typename RT, typename C, typename... Args>
1127  inline Thread<RT>
1128  new_thread(RT (C::*fun_ptr)(Args...) const,
1129  typename identity<const C>::type &c,
1130  typename identity<Args>::type... args)
1131  {
1132  return new_thread(std::function<RT()>(std::bind(
1133  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1134  }
1135 # endif
1136 
1137  // ------------------------ ThreadGroup -------------------------------------
1138 
1147  template <typename RT = void>
1149  {
1150  public:
1154  ThreadGroup &
1156  {
1157  threads.push_back(t);
1158  return *this;
1159  }
1160 
1167  void
1168  join_all() const
1169  {
1170  for (typename std::list<Thread<RT>>::const_iterator t = threads.begin();
1171  t != threads.end();
1172  ++t)
1173  t->join();
1174  }
1175 
1176  private:
1180  std::list<Thread<RT>> threads;
1181  };
1182 
1183 
1184  template <typename>
1185  class Task;
1186 
1187 
1188  namespace internal
1189  {
1190 # ifdef DEAL_II_WITH_THREADS
1191 
1192  template <typename>
1193  struct TaskDescriptor;
1194 
1198  template <typename RT>
1199  struct TaskEntryPoint : public tbb::task
1200  {
1201  TaskEntryPoint(TaskDescriptor<RT> &task_descriptor)
1202  : task_descriptor(task_descriptor)
1203  {}
1204 
1205  virtual tbb::task *
1206  execute() override
1207  {
1208  // call the function object and put the return value into the
1209  // proper place
1210  try
1211  {
1212  call(task_descriptor.function, task_descriptor.ret_val);
1213  }
1214  catch (const std::exception &exc)
1215  {
1216  internal::handle_std_exception(exc);
1217  }
1218  catch (...)
1219  {
1220  internal::handle_unknown_exception();
1221  }
1222  return nullptr;
1223  }
1224 
1228  TaskDescriptor<RT> &task_descriptor;
1229  };
1230 
1252  template <typename RT>
1253  struct TaskDescriptor
1254  {
1255  private:
1259  std::function<RT()> function;
1260 
1269  tbb::task *task;
1270 
1274  return_value<RT> ret_val;
1275 
1279  bool task_is_done;
1280 
1281  public:
1285  TaskDescriptor(const std::function<RT()> &function);
1286 
1292  TaskDescriptor();
1293 
1298  TaskDescriptor(const TaskDescriptor &);
1299 
1303  ~TaskDescriptor();
1304 
1309  TaskDescriptor &
1310  operator=(const TaskDescriptor &);
1311 
1318  void
1319  queue_task();
1320 
1326  void
1327  join();
1328 
1329 
1330  template <typename>
1331  friend struct TaskEntryPoint;
1332  friend class ::Threads::Task<RT>;
1333  };
1334 
1335 
1336 
1337  template <typename RT>
1338  inline TaskDescriptor<RT>::TaskDescriptor(
1339  const std::function<RT()> &function)
1340  : function(function)
1341  , task(nullptr)
1342  , task_is_done(false)
1343  {}
1344 
1345 
1346  template <typename RT>
1347  inline void
1348  TaskDescriptor<RT>::queue_task()
1349  {
1350  // use the pattern described in the TBB book on pages 230/231
1351  // ("Start a large task in parallel with the main program")
1352  task = new (tbb::task::allocate_root()) tbb::empty_task;
1353  task->set_ref_count(2);
1354 
1355  tbb::task *worker =
1356  new (task->allocate_child()) TaskEntryPoint<RT>(*this);
1357 
1358  tbb::task::spawn(*worker);
1359  }
1360 
1361 
1362 
1363  template <typename RT>
1364  TaskDescriptor<RT>::TaskDescriptor()
1365  : task_is_done(false)
1366  {
1367  Assert(false, ExcInternalError());
1368  }
1369 
1370 
1371 
1372  template <typename RT>
1373  TaskDescriptor<RT>::TaskDescriptor(const TaskDescriptor &)
1374  : task_is_done(false)
1375  {
1376  // we shouldn't be getting here -- task descriptors
1377  // can't be copied
1378  Assert(false, ExcInternalError());
1379  }
1380 
1381 
1382 
1383  template <typename RT>
1384  inline TaskDescriptor<RT>::~TaskDescriptor()
1385  {
1386  // wait for the task to complete for sure
1387  join();
1388 
1389  // now destroy the empty task structure. the book recommends to
1390  // spawn it as well and let the scheduler destroy the object
1391  // when done, but this has the disadvantage that the scheduler
1392  // may not get to actually finishing the task before it goes out
1393  // of scope (at the end of the program, or if a thread is done
1394  // on which it was run) and then we would get a hard-to-decipher
1395  // warning about unfinished tasks when the scheduler "goes out
1396  // of the arena". rather, let's explicitly destroy the empty
1397  // task object. before that, make sure that the task has been
1398  // shut down, expressed by a zero reference count
1399  AssertNothrow(task != nullptr, ExcInternalError());
1400  AssertNothrow(task->ref_count() == 0, ExcInternalError());
1401  task->destroy(*task);
1402  }
1403 
1404 
1405  template <typename RT>
1406  TaskDescriptor<RT> &
1407  TaskDescriptor<RT>::operator=(const TaskDescriptor &)
1408  {
1409  // we shouldn't be getting here -- task descriptors
1410  // can't be copied
1411  Assert(false, ExcInternalError());
1412  return *this;
1413  }
1414 
1415 
1416  template <typename RT>
1417  inline void
1418  TaskDescriptor<RT>::join()
1419  {
1420  // if the task is already done, just return. this makes sure we
1421  // call tbb::Task::wait_for_all() exactly once, as required by
1422  // TBB. we could also get the reference count of task for doing
1423  // this, but that is usually slower. note that this does not
1424  // work when the thread calling this function is not the same as
1425  // the one that initialized the task.
1426  //
1427  // TODO: can we assert that no other thread tries to end the
1428  // task?
1429  if (task_is_done == true)
1430  return;
1431 
1432  // let TBB wait for the task to complete.
1433  task_is_done = true;
1434  task->wait_for_all();
1435  }
1436 
1437 
1438 
1439 # else // no threading enabled
1440 
1445  template <typename RT>
1446  struct TaskDescriptor
1447  {
1451  return_value<RT> ret_val;
1452 
1457  TaskDescriptor(const std::function<RT()> &function)
1458  {
1459  call(function, ret_val);
1460  }
1461 
1466  static void
1467  join()
1468  {}
1469 
1474  static void
1475  queue_task()
1476  {}
1477  };
1478 
1479 # endif
1480 
1481  } // namespace internal
1482 
1483 
1484 
1495  template <typename RT = void>
1496  class Task
1497  {
1498  public:
1506  Task(const std::function<RT()> &function_object)
1507  {
1508  // create a task descriptor and tell it to queue itself up with
1509  // the scheduling system
1510  task_descriptor =
1511  std::make_shared<internal::TaskDescriptor<RT>>(function_object);
1512  task_descriptor->queue_task();
1513  }
1514 
1515 
1516 
1525  Task() = default;
1526 
1527 
1528 
1540  void
1541  join() const
1542  {
1543  AssertThrow(joinable(), ExcNoTask());
1544  task_descriptor->join();
1545  }
1546 
1559  bool
1560  joinable() const
1561  {
1562  return (task_descriptor !=
1563  std::shared_ptr<internal::TaskDescriptor<RT>>());
1564  }
1565 
1566 
1610  typename internal::return_value<RT>::reference_type
1612  {
1613  join();
1614  return task_descriptor->ret_val.get();
1615  }
1616 
1617 
1623  bool
1624  operator==(const Task &t) const
1625  {
1626  AssertThrow(joinable(), ExcNoTask());
1627  return task_descriptor == t.task_descriptor;
1628  }
1629 
1638  DeclExceptionMsg(ExcNoTask,
1639  "The current object is not associated with a task that "
1640  "can be joined. It may have been detached, or you "
1641  "may have already joined it in the past.");
1643  private:
1648  std::shared_ptr<internal::TaskDescriptor<RT>> task_descriptor;
1649  };
1650 
1651 
1652 
1661  template <typename RT>
1662  inline Task<RT>
1663  new_task(const std::function<RT()> &function)
1664  {
1665  return Task<RT>(function);
1666  }
1667 
1668 
1669 
1734  template <typename FunctionObjectType>
1735  inline auto
1736  new_task(FunctionObjectType function_object)
1738  {
1739  using return_type = decltype(function_object());
1741  return Task<return_type>(std::function<return_type()>(function_object));
1742  }
1743 
1744 
1745 
1752  template <typename RT, typename... Args>
1753  inline Task<RT>
1754  new_task(RT (*fun_ptr)(Args...), typename identity<Args>::type... args)
1755  {
1756  return new_task(std::function<RT()>(
1757  std::bind(fun_ptr, internal::maybe_make_ref<Args>::act(args)...)));
1758  }
1759 
1760 
1761 
1767  template <typename RT, typename C, typename... Args>
1768  inline Task<RT>
1769  new_task(RT (C::*fun_ptr)(Args...),
1770  typename identity<C>::type &c,
1771  typename identity<Args>::type... args)
1772  {
1773  return new_task(std::function<RT()>(std::bind(
1774  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1775  }
1776 
1777 # ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG
1778 
1783  template <typename RT, typename C, typename... Args>
1784  inline Task<RT>
1785  new_task(RT (C::*fun_ptr)(Args...) const,
1786  typename identity<const C>::type &c,
1787  typename identity<Args>::type... args)
1788  {
1789  return new_task(std::function<RT()>(std::bind(
1790  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1791  }
1792 # endif
1793 
1794 
1795  // ------------------------ TaskGroup -------------------------------------
1796 
1810  template <typename RT = void>
1812  {
1813  public:
1817  TaskGroup &
1819  {
1820  tasks.push_back(t);
1821  return *this;
1822  }
1823 
1830  void
1831  join_all() const
1832  {
1833  for (typename std::list<Task<RT>>::const_iterator t = tasks.begin();
1834  t != tasks.end();
1835  ++t)
1836  t->join();
1837  }
1838 
1839  private:
1843  std::list<Task<RT>> tasks;
1844  };
1845 
1846 } // namespace Threads
1847 
1853 //---------------------------------------------------------------------------
1854 DEAL_II_NAMESPACE_CLOSE
1855 // end of #ifndef dealii_thread_management_h
1856 #endif
1857 //---------------------------------------------------------------------------
std::shared_ptr< internal::ThreadDescriptor< RT > > thread_descriptor
#define AssertNothrow(cond, exc)
Definition: exceptions.h:1471
unsigned int this_thread_id()
bool valid() const
Task< RT > new_task(const std::function< RT()> &function)
Thread(const Thread< RT > &t)
TaskDescriptor< RT > & task_descriptor
Mutex(const Mutex &)
std::list< Task< RT > > tasks
internal::return_value< RT >::reference_type return_value()
void join() const
std::shared_ptr< return_value< RT > > ret_val
STL namespace.
#define AssertThrow(cond, exc)
Definition: exceptions.h:1519
bool operator==(const Task &t) const
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)
Mutex & operator=(const Mutex &)
std::condition_variable condition_variable
#define Assert(cond, exc)
Definition: exceptions.h:1407
static void initialize_multithreading()
std::shared_ptr< internal::TaskDescriptor< RT > > task_descriptor
#define DeclExceptionMsg(Exception, defaulttext)
Definition: exceptions.h:496
Mutex()=default
unsigned int n_existing_threads()
bool joinable() const
Thread(const std::function< RT()> &function)
std::lock_guard< std::mutex > ScopedLock
internal::return_value< RT >::reference_type return_value()
bool operator==(const Thread &t) const
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)
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()