Reference documentation for deal.II version Git d902c8c1aa 2019-12-15 00:36:45 -0500
\(\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/std_cxx17/tuple.h>
25 # include <deal.II/base/template_constraints.h>
26 
27 # include <condition_variable>
28 # include <functional>
29 # include <iterator>
30 # include <list>
31 # include <memory>
32 # include <mutex>
33 # include <tuple>
34 # include <utility>
35 # include <vector>
36 DEAL_II_DISABLE_EXTRA_DIAGNOSTICS
37 # ifdef DEAL_II_WITH_THREADS
38 # include <thread>
39 # ifdef DEAL_II_USE_MT_POSIX
40 # include <pthread.h>
41 # endif
42 # include <tbb/task.h>
43 # include <tbb/tbb_stddef.h>
44 # endif
45 DEAL_II_ENABLE_EXTRA_DIAGNOSTICS
46 
47 
48 
49 DEAL_II_NAMESPACE_OPEN
50 
53 
54 
62 namespace Threads
63 {
89  class Mutex : public std::mutex
90  {
91  public:
112  using ScopedLock DEAL_II_DEPRECATED = std::lock_guard<std::mutex>;
113 
117  Mutex() = default;
118 
123  Mutex(const Mutex &)
124  : std::mutex()
125  {}
126 
127 
132  Mutex &
133  operator=(const Mutex &)
134  {
135  return *this;
136  }
137 
138 
145  DEAL_II_DEPRECATED
146  inline void
148  {
149  this->lock();
150  }
151 
158  DEAL_II_DEPRECATED
159  inline void
161  {
162  this->unlock();
163  }
164  };
165 
166 
175  class DEAL_II_DEPRECATED ConditionVariable
176  {
177  public:
182  inline void
184  {
185  condition_variable.notify_one();
186  }
187 
192  inline void
194  {
195  condition_variable.notify_all();
196  }
197 
207  inline void
208  wait(Mutex &mutex)
209  {
210  std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
211  condition_variable.wait(lock);
212  }
213 
214  private:
218  std::condition_variable condition_variable;
219  };
220 
221 # ifdef DEAL_II_USE_MT_POSIX
222 
240  class DEAL_II_DEPRECATED PosixThreadBarrier
241  {
242  public:
246  PosixThreadBarrier(const unsigned int count,
247  const char * name = nullptr,
248  void * arg = nullptr);
249 
254 
261  int
262  wait();
263 
264  private:
269 # ifndef DEAL_II_USE_MT_POSIX_NO_BARRIERS
270  pthread_barrier_t barrier;
271 # else
272  unsigned int count;
273 # endif
274  };
275 
276 
284  using Barrier DEAL_II_DEPRECATED = PosixThreadBarrier;
285 # endif
286 } // namespace Threads
287 
288 
289 namespace Threads
290 {
329  DEAL_II_DEPRECATED
330  unsigned int
332 
346  DEAL_II_DEPRECATED
347  unsigned int
348  this_thread_id();
349 
364  template <typename ForwardIterator>
365  std::vector<std::pair<ForwardIterator, ForwardIterator>>
366  split_range(const ForwardIterator &begin,
367  const ForwardIterator &end,
368  const unsigned int n_intervals);
369 
378  std::vector<std::pair<unsigned int, unsigned int>>
379  split_interval(const unsigned int begin,
380  const unsigned int end,
381  const unsigned int n_intervals);
382 
394  namespace internal
395  {
411  [[noreturn]] void
412  handle_std_exception(const std::exception &exc);
413 
421  [[noreturn]] void
422  handle_unknown_exception();
423 
431  void
432  register_thread();
433 
441  void
442  deregister_thread();
443  } // namespace internal
444 
449 } // namespace Threads
450 
451 /* ----------- implementation of functions in namespace Threads ---------- */
452 # ifndef DOXYGEN
453 namespace Threads
454 {
455  template <typename ForwardIterator>
456  std::vector<std::pair<ForwardIterator, ForwardIterator>>
457  split_range(const ForwardIterator &begin,
458  const ForwardIterator &end,
459  const unsigned int n_intervals)
460  {
461  using IteratorPair = std::pair<ForwardIterator, ForwardIterator>;
462 
463  // in non-multithreaded mode, we often have the case that this
464  // function is called with n_intervals==1, so have a shortcut here
465  // to handle that case efficiently
466 
467  if (n_intervals == 1)
468  return (std::vector<IteratorPair>(1, IteratorPair(begin, end)));
469 
470  // if more than one interval requested, do the full work
471  const unsigned int n_elements = std::distance(begin, end);
472  const unsigned int n_elements_per_interval = n_elements / n_intervals;
473  const unsigned int residual = n_elements % n_intervals;
474 
475  std::vector<IteratorPair> return_values(n_intervals);
476 
477  return_values[0].first = begin;
478  for (unsigned int i = 0; i < n_intervals; ++i)
479  {
480  if (i != n_intervals - 1)
481  {
482  return_values[i].second = return_values[i].first;
483  // note: the cast is performed to avoid a warning of gcc
484  // that in the library `dist>=0' is checked (dist has a
485  // template type, which here is unsigned if no cast is
486  // performed)
487  std::advance(return_values[i].second,
488  static_cast<signed int>(n_elements_per_interval));
489  // distribute residual in division equally among the first
490  // few subintervals
491  if (i < residual)
492  ++return_values[i].second;
493 
494  return_values[i + 1].first = return_values[i].second;
495  }
496  else
497  return_values[i].second = end;
498  }
499  return return_values;
500  }
501 } // namespace Threads
502 
503 # endif // DOXYGEN
504 
505 namespace Threads
506 {
507  namespace internal
508  {
517  template <typename RT>
518  struct return_value
519  {
520  private:
521  RT value;
522 
523  public:
524  using reference_type = RT &;
525 
526  inline return_value()
527  : value()
528  {}
529 
530  inline reference_type
531  get()
532  {
533  return value;
534  }
535 
536  inline void
537  set(RT &&v)
538  {
539  value = std::move(v);
540  }
541  };
542 
543 
553  template <typename RT>
554  struct return_value<RT &>
555  {
556  private:
557  RT *value;
558 
559  public:
560  using reference_type = RT &;
561 
562  inline return_value()
563  : value(nullptr)
564  {}
565 
566  inline reference_type
567  get() const
568  {
569  return *value;
570  }
571 
572  inline void
573  set(RT &v)
574  {
575  value = &v;
576  }
577  };
578 
579 
588  template <>
589  struct return_value<void>
590  {
591  using reference_type = void;
592 
593  static inline void
594  get()
595  {}
596  };
597  } // namespace internal
598 
599 
600 
601  namespace internal
602  {
603  template <typename RT>
604  inline void
605  call(const std::function<RT()> & function,
606  internal::return_value<RT> &ret_val)
607  {
608  ret_val.set(function());
609  }
610 
611 
612  inline void
613  call(const std::function<void()> &function, internal::return_value<void> &)
614  {
615  function();
616  }
617  } // namespace internal
618 
619 
620 
621  namespace internal
622  {
623 # ifdef DEAL_II_WITH_THREADS
624 
635  template <typename RT>
637  {
641  std::thread thread;
642 
651  std::shared_ptr<return_value<RT>> ret_val;
652 
685 
690 
695  : thread_is_active(false)
696  {}
697 
699  {
700  if (!thread_is_active)
701  return;
702  thread.detach();
703  thread_is_active = false;
704  }
705 
710  void
711  start(const std::function<RT()> &function)
712  {
713  thread_is_active = true;
714  ret_val = std::make_shared<return_value<RT>>();
715  thread = std::thread(thread_entry_point, function, ret_val);
716  }
717 
718 
722  void
724  {
725  // see if the thread hasn't been joined yet. if it has, then
726  // join() is a no-op. use schmidt's double-checking strategy
727  // to use the mutex only when necessary
728  if (thread_is_active == false)
729  return;
730 
731  Mutex::ScopedLock lock(thread_is_active_mutex);
732  if (thread_is_active == true)
733  {
734  Assert(thread.joinable(), ExcInternalError());
735  thread.join();
736  thread_is_active = false;
737  }
738  }
739 
740  private:
744  static void
745  thread_entry_point(const std::function<RT()> & function,
746  std::shared_ptr<return_value<RT>> ret_val)
747  {
748  // call the function in question. since an exception that is
749  // thrown from one of the called functions will not propagate
750  // to the main thread, it will kill the program if not treated
751  // here before we return to the operating system's thread
752  // library
753  internal::register_thread();
754  try
755  {
756  call(function, *ret_val);
757  }
758  catch (const std::exception &exc)
759  {
760  internal::handle_std_exception(exc);
761  }
762  catch (...)
763  {
764  internal::handle_unknown_exception();
765  }
766  internal::deregister_thread();
767  }
768  };
769 
770 # else
771 
779  template <typename RT>
780  struct ThreadDescriptor
781  {
786  std::shared_ptr<return_value<RT>> ret_val;
787 
792  void
793  start(const std::function<RT()> &function)
794  {
795  ret_val = std::make_shared<return_value<RT>>();
796  call(function, *ret_val);
797  }
798 
802  void
803  join()
804  {}
805  };
806 
807 # endif
808  } // namespace internal
809 
810 
833  template <typename RT = void>
834  class Thread
835  {
836  public:
840  Thread(const std::function<RT()> &function)
841  : thread_descriptor(new internal::ThreadDescriptor<RT>())
842  {
843  // in a second step, start the thread.
844  thread_descriptor->start(function);
845  }
846 
852  Thread() = default;
853 
857  Thread(const Thread<RT> &t)
858  : thread_descriptor(t.thread_descriptor)
859  {}
860 
866  void
867  join() const
868  {
869  if (thread_descriptor)
870  thread_descriptor->join();
871  }
872 
916  typename internal::return_value<RT>::reference_type
918  {
919  join();
920  return thread_descriptor->ret_val->get();
921  }
922 
927  bool
928  valid() const
929  {
930  return static_cast<bool>(thread_descriptor);
931  }
932 
933 
939  bool
940  operator==(const Thread &t) const
941  {
942  return thread_descriptor == t.thread_descriptor;
943  }
944 
945  private:
951  std::shared_ptr<internal::ThreadDescriptor<RT>> thread_descriptor;
952  };
953 
954 
955  namespace internal
956  {
964  template <typename T>
966  {
967  static T
968  act(T &t)
969  {
970  return t;
971  }
972  };
973 
974 
975 
983  template <typename T>
984  struct maybe_make_ref<T &>
985  {
986  static std::reference_wrapper<T>
987  act(T &t)
988  {
989  return std::ref(t);
990  }
991  };
992  } // namespace internal
993 
994 
995 
996  // ----------- thread starters for functions not taking any parameters
997 
1006  template <typename RT>
1007  inline Thread<RT>
1008  new_thread(const std::function<RT()> &function)
1009  {
1010  return Thread<RT>(function);
1011  }
1012 
1013 
1014 
1079  template <typename FunctionObjectType>
1080  inline auto
1081  new_thread(FunctionObjectType function_object)
1083  {
1084  using return_type = decltype(function_object());
1085  return Thread<return_type>(std::function<return_type()>(function_object));
1086  }
1087 
1088 
1089 
1096  template <typename RT, typename... Args>
1097  inline Thread<RT>
1098  new_thread(RT (*fun_ptr)(Args...), typename identity<Args>::type... args)
1099  {
1100  auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1101  return new_thread(
1102  [dummy, fun_ptr]() -> RT { return std_cxx17::apply(fun_ptr, dummy); });
1103  }
1104 
1105 
1106 
1112  template <typename RT, typename C, typename... Args>
1113  inline Thread<RT>
1114  new_thread(RT (C::*fun_ptr)(Args...),
1115  typename identity<C>::type &c,
1116  typename identity<Args>::type... args)
1117  {
1118  return new_thread(std::function<RT()>(std::bind(
1119  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1120  }
1121 
1122 # ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG
1123 
1128  template <typename RT, typename C, typename... Args>
1129  inline Thread<RT>
1130  new_thread(RT (C::*fun_ptr)(Args...) const,
1131  typename identity<const C>::type &c,
1132  typename identity<Args>::type... args)
1133  {
1134  return new_thread(std::function<RT()>(std::bind(
1135  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1136  }
1137 # endif
1138 
1139  // ------------------------ ThreadGroup -------------------------------------
1140 
1149  template <typename RT = void>
1151  {
1152  public:
1156  ThreadGroup &
1158  {
1159  threads.push_back(t);
1160  return *this;
1161  }
1162 
1169  void
1170  join_all() const
1171  {
1172  for (typename std::list<Thread<RT>>::const_iterator t = threads.begin();
1173  t != threads.end();
1174  ++t)
1175  t->join();
1176  }
1177 
1178  private:
1182  std::list<Thread<RT>> threads;
1183  };
1184 
1185 
1186  template <typename>
1187  class Task;
1188 
1189 
1190  namespace internal
1191  {
1192 # ifdef DEAL_II_WITH_THREADS
1193 
1194  template <typename>
1195  struct TaskDescriptor;
1196 
1200  template <typename RT>
1201  struct TaskEntryPoint : public tbb::task
1202  {
1203  TaskEntryPoint(TaskDescriptor<RT> &task_descriptor)
1204  : task_descriptor(task_descriptor)
1205  {}
1206 
1207  virtual tbb::task *
1208  execute() override
1209  {
1210  // call the function object and put the return value into the
1211  // proper place
1212  try
1213  {
1214  call(task_descriptor.function, task_descriptor.ret_val);
1215  }
1216  catch (const std::exception &exc)
1217  {
1218  internal::handle_std_exception(exc);
1219  }
1220  catch (...)
1221  {
1222  internal::handle_unknown_exception();
1223  }
1224  return nullptr;
1225  }
1226 
1230  TaskDescriptor<RT> &task_descriptor;
1231  };
1232 
1254  template <typename RT>
1255  struct TaskDescriptor
1256  {
1257  private:
1261  std::function<RT()> function;
1262 
1271  tbb::task *task;
1272 
1276  return_value<RT> ret_val;
1277 
1281  bool task_is_done;
1282 
1283  public:
1287  TaskDescriptor(const std::function<RT()> &function);
1288 
1294  TaskDescriptor();
1295 
1300  TaskDescriptor(const TaskDescriptor &);
1301 
1305  ~TaskDescriptor();
1306 
1311  TaskDescriptor &
1312  operator=(const TaskDescriptor &);
1313 
1320  void
1321  queue_task();
1322 
1328  void
1329  join();
1330 
1331 
1332  template <typename>
1333  friend struct TaskEntryPoint;
1334  friend class ::Threads::Task<RT>;
1335  };
1336 
1337 
1338 
1339  template <typename RT>
1340  inline TaskDescriptor<RT>::TaskDescriptor(
1341  const std::function<RT()> &function)
1342  : function(function)
1343  , task(nullptr)
1344  , task_is_done(false)
1345  {}
1346 
1347 
1348  template <typename RT>
1349  inline void
1350  TaskDescriptor<RT>::queue_task()
1351  {
1352  // use the pattern described in the TBB book on pages 230/231
1353  // ("Start a large task in parallel with the main program")
1354  task = new (tbb::task::allocate_root()) tbb::empty_task;
1355  task->set_ref_count(2);
1356 
1357  tbb::task *worker =
1358  new (task->allocate_child()) TaskEntryPoint<RT>(*this);
1359 
1360  tbb::task::spawn(*worker);
1361  }
1362 
1363 
1364 
1365  template <typename RT>
1366  TaskDescriptor<RT>::TaskDescriptor()
1367  : task_is_done(false)
1368  {
1369  Assert(false, ExcInternalError());
1370  }
1371 
1372 
1373 
1374  template <typename RT>
1375  TaskDescriptor<RT>::TaskDescriptor(const TaskDescriptor &)
1376  : task_is_done(false)
1377  {
1378  // we shouldn't be getting here -- task descriptors
1379  // can't be copied
1380  Assert(false, ExcInternalError());
1381  }
1382 
1383 
1384 
1385  template <typename RT>
1386  inline TaskDescriptor<RT>::~TaskDescriptor()
1387  {
1388  // wait for the task to complete for sure
1389  join();
1390 
1391  // now destroy the empty task structure. the book recommends to
1392  // spawn it as well and let the scheduler destroy the object
1393  // when done, but this has the disadvantage that the scheduler
1394  // may not get to actually finishing the task before it goes out
1395  // of scope (at the end of the program, or if a thread is done
1396  // on which it was run) and then we would get a hard-to-decipher
1397  // warning about unfinished tasks when the scheduler "goes out
1398  // of the arena". rather, let's explicitly destroy the empty
1399  // task object. before that, make sure that the task has been
1400  // shut down, expressed by a zero reference count
1401  AssertNothrow(task != nullptr, ExcInternalError());
1402  AssertNothrow(task->ref_count() == 0, ExcInternalError());
1403  task->destroy(*task);
1404  }
1405 
1406 
1407  template <typename RT>
1408  TaskDescriptor<RT> &
1409  TaskDescriptor<RT>::operator=(const TaskDescriptor &)
1410  {
1411  // we shouldn't be getting here -- task descriptors
1412  // can't be copied
1413  Assert(false, ExcInternalError());
1414  return *this;
1415  }
1416 
1417 
1418  template <typename RT>
1419  inline void
1420  TaskDescriptor<RT>::join()
1421  {
1422  // if the task is already done, just return. this makes sure we
1423  // call tbb::Task::wait_for_all() exactly once, as required by
1424  // TBB. we could also get the reference count of task for doing
1425  // this, but that is usually slower. note that this does not
1426  // work when the thread calling this function is not the same as
1427  // the one that initialized the task.
1428  //
1429  // TODO: can we assert that no other thread tries to end the
1430  // task?
1431  if (task_is_done == true)
1432  return;
1433 
1434  // let TBB wait for the task to complete.
1435  task_is_done = true;
1436  task->wait_for_all();
1437  }
1438 
1439 
1440 
1441 # else // no threading enabled
1442 
1447  template <typename RT>
1448  struct TaskDescriptor
1449  {
1453  return_value<RT> ret_val;
1454 
1459  TaskDescriptor(const std::function<RT()> &function)
1460  {
1461  call(function, ret_val);
1462  }
1463 
1468  static void
1469  join()
1470  {}
1471 
1476  static void
1477  queue_task()
1478  {}
1479  };
1480 
1481 # endif
1482 
1483  } // namespace internal
1484 
1485 
1486 
1497  template <typename RT = void>
1498  class Task
1499  {
1500  public:
1508  Task(const std::function<RT()> &function_object)
1509  {
1510  // create a task descriptor and tell it to queue itself up with
1511  // the scheduling system
1512  task_descriptor =
1513  std::make_shared<internal::TaskDescriptor<RT>>(function_object);
1514  task_descriptor->queue_task();
1515  }
1516 
1517 
1518 
1527  Task() = default;
1528 
1529 
1530 
1542  void
1543  join() const
1544  {
1545  AssertThrow(joinable(), ExcNoTask());
1546  task_descriptor->join();
1547  }
1548 
1561  bool
1562  joinable() const
1563  {
1564  return (task_descriptor !=
1565  std::shared_ptr<internal::TaskDescriptor<RT>>());
1566  }
1567 
1568 
1612  typename internal::return_value<RT>::reference_type
1614  {
1615  join();
1616  return task_descriptor->ret_val.get();
1617  }
1618 
1619 
1625  bool
1626  operator==(const Task &t) const
1627  {
1628  AssertThrow(joinable(), ExcNoTask());
1629  return task_descriptor == t.task_descriptor;
1630  }
1631 
1640  DeclExceptionMsg(ExcNoTask,
1641  "The current object is not associated with a task that "
1642  "can be joined. It may have been detached, or you "
1643  "may have already joined it in the past.");
1645  private:
1650  std::shared_ptr<internal::TaskDescriptor<RT>> task_descriptor;
1651  };
1652 
1653 
1654 
1663  template <typename RT>
1664  inline Task<RT>
1665  new_task(const std::function<RT()> &function)
1666  {
1667  return Task<RT>(function);
1668  }
1669 
1670 
1671 
1736  template <typename FunctionObjectType>
1737  inline auto
1738  new_task(FunctionObjectType function_object)
1740  {
1741  using return_type = decltype(function_object());
1743  return Task<return_type>(std::function<return_type()>(function_object));
1744  }
1745 
1746 
1747 
1754  template <typename RT, typename... Args>
1755  inline Task<RT>
1756  new_task(RT (*fun_ptr)(Args...), typename identity<Args>::type... args)
1757  {
1758  auto dummy = std::make_tuple(internal::maybe_make_ref<Args>::act(args)...);
1759  return new_task(
1760  [dummy, fun_ptr]() -> RT { return std_cxx17::apply(fun_ptr, dummy); });
1761  }
1762 
1763 
1764 
1770  template <typename RT, typename C, typename... Args>
1771  inline Task<RT>
1772  new_task(RT (C::*fun_ptr)(Args...),
1773  typename identity<C>::type &c,
1774  typename identity<Args>::type... args)
1775  {
1776  return new_task(std::function<RT()>(std::bind(
1777  fun_ptr, std::ref(c), internal::maybe_make_ref<Args>::act(args)...)));
1778  }
1779 
1780 # ifndef DEAL_II_CONST_MEMBER_DEDUCTION_BUG
1781 
1786  template <typename RT, typename C, typename... Args>
1787  inline Task<RT>
1788  new_task(RT (C::*fun_ptr)(Args...) const,
1789  typename identity<const C>::type &c,
1790  typename identity<Args>::type... args)
1791  {
1792  return new_task(std::function<RT()>(std::bind(
1793  fun_ptr, std::cref(c), internal::maybe_make_ref<Args>::act(args)...)));
1794  }
1795 # endif
1796 
1797 
1798  // ------------------------ TaskGroup -------------------------------------
1799 
1813  template <typename RT = void>
1815  {
1816  public:
1820  TaskGroup &
1822  {
1823  tasks.push_back(t);
1824  return *this;
1825  }
1826 
1833  void
1834  join_all() const
1835  {
1836  for (typename std::list<Task<RT>>::const_iterator t = tasks.begin();
1837  t != tasks.end();
1838  ++t)
1839  t->join();
1840  }
1841 
1842  private:
1846  std::list<Task<RT>> tasks;
1847  };
1848 
1849 } // namespace Threads
1850 
1856 //---------------------------------------------------------------------------
1857 DEAL_II_NAMESPACE_CLOSE
1858 // end of #ifndef dealii_thread_management_h
1859 #endif
1860 //---------------------------------------------------------------------------
std::shared_ptr< internal::ThreadDescriptor< RT > > thread_descriptor
#define AssertNothrow(cond, exc)
Definition: exceptions.h:1475
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:1523
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:1411
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()