Reference documentation for deal.II version Git ce7b8fd3ef 2020-09-19 21:33:14 +0200
\(\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_local_storage.h
Go to the documentation of this file.
1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2011 - 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_local_storage_h
17 # define dealii_thread_local_storage_h
18 
19 
20 # include <deal.II/base/config.h>
21 
22 # include <deal.II/base/exceptions.h>
23 
24 # include <list>
25 # include <map>
26 # include <shared_mutex>
27 # include <thread>
28 # include <vector>
29 
31 
34 
35 # ifndef DOXYGEN
36 class LogStream;
37 # endif
38 
39 namespace Threads
40 {
41 # ifndef DOXYGEN
42  namespace internal
43  {
44  /*
45  * Workaround: The standard unfortunately has an unfortunate design
46  * "flaw" in the std::is_copy_constructible type trait
47  * when it comes to STL containers and containing non-copyable objects
48  * T. The type trait is true even though any attempted invocation leads
49  * to a compilation error. Work around this issue by unpacking some
50  * commonly used containers:
51  */
52  template <typename T>
53  struct unpack_container
54  {
55  using type = T;
56  };
57 
58  template <typename T, typename A>
59  struct unpack_container<std::vector<T, A>>
60  {
61  using type = T;
62  };
63 
64  template <typename T, typename A>
65  struct unpack_container<std::list<T, A>>
66  {
67  using type = T;
68  };
69  } // namespace internal
70 # endif
71 
99  template <typename T>
101  {
102  static_assert(
103  std::is_copy_constructible<
104  typename internal::unpack_container<T>::type>::value ||
105  std::is_default_constructible<T>::value,
106  "The stored type must be either copyable, or default constructible");
107 
108  public:
113  ThreadLocalStorage() = default;
114 
119 
125 
131  explicit ThreadLocalStorage(const T &t);
132 
138  explicit ThreadLocalStorage(T &&t);
139 
144  operator=(const ThreadLocalStorage &t);
145 
150  operator=(ThreadLocalStorage &&t) noexcept;
151 
165  T &
166  get();
167 
172  T &
173  get(bool &exists);
174 
181  operator T &();
182 
196  operator=(const T &t);
197 
212  operator=(T &&t);
213 
233  void
234  clear();
235 
236  private:
240  std::map<std::thread::id, T> data;
241 
250 # ifdef DEAL_II_HAVE_CXX17
251  mutable std::shared_mutex insertion_mutex;
252 # else
253  mutable std::shared_timed_mutex insertion_mutex;
254 # endif
255 
259  std::shared_ptr<const T> exemplar;
260 
261  friend class ::LogStream;
262  };
263 } // namespace Threads
268 # ifndef DOXYGEN
269 namespace Threads
270 {
271  // ----------------- inline and template functions --------------------------
272 
273 
274  template <typename T>
276  : exemplar(t.exemplar)
277  {
278  // Raise a reader lock while we are populating our own data in order to
279  // avoid copying over an invalid state.
280  std::shared_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
281  data = t.data;
282  }
283 
284 
285 
286  template <typename T>
288  : exemplar(std::move(t.exemplar))
289  {
290  // We are nice and raise the writer lock before copying over internal
291  // data structures from the argument.
292  //
293  // The point is a bit moot, though: Users of ThreadLocalStorage
294  // typically obtain their thread's thread-local object through the
295  // get() function. That function also acquires the lock, but
296  // whether or not we do that here really doesn't make any
297  // difference in terms of correctness: If another thread manages
298  // to call get() just before we get here, then the result of that
299  // get() function immediately becomes invalid; if it manages to
300  // call get() at the same time as this function if there were no
301  // locking here, it might access undefined state; and if it
302  // manages to call get() just after we moved away the state --
303  // well, then it just got lucky to escape the race condition, but
304  // the race condition is still there.
305  //
306  // On the other hand, there is no harm in doing at least
307  // conceptually the right thing, so ask for that lock:
308  std::unique_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
309  data = std::move(t.data);
310  }
311 
312 
313 
314  template <typename T>
316  : exemplar(std::make_shared<const T>(t))
317  {}
318 
319 
320 
321  template <typename T>
323  : exemplar(std::make_shared<T>(std::forward<T>(t)))
324  {}
325 
326 
327 
328  template <typename T>
329  inline ThreadLocalStorage<T> &
331  {
332  // We need to raise the reader lock of the argument and our writer lock
333  // while copying internal data structures.
334  std::shared_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
335  std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
336 
337  data = t.data;
338  }
339 
340 
341 
342  template <typename T>
343  inline ThreadLocalStorage<T> &
345  {
346  // We need to raise the writer lock of the argument (because we're
347  // moving information *away* from that object) and the writer lock
348  // of our object while copying internal data structures.
349  //
350  // That said, the same issue with acquiring the source lock as
351  // with the move constructor above applies here as well.
352  std::unique_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
353  std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
354 
355  data = std::move(t.data);
356 
357  return *this;
358  }
359 
360 
361 # ifndef DOXYGEN
362  namespace internal
363  {
364  /*
365  * We have to make sure not to call "data.emplace(id, *exemplar)" if
366  * the corresponding element is not copy constructible. We use some
367  * SFINAE magic to work around the fact that C++14 does not have
368  * "if constexpr".
369  */
370  template <typename T>
371  typename std::enable_if_t<
372  std::is_copy_constructible<typename unpack_container<T>::type>::value,
373  T &>
374  construct_element(std::map<std::thread::id, T> & data,
375  const std::thread::id & id,
376  const std::shared_ptr<const T> &exemplar)
377  {
378  if (exemplar)
379  {
380  const auto it = data.emplace(id, *exemplar).first;
381  return it->second;
382  }
383  return data[id];
384  }
385 
386  template <typename T>
387  typename std::enable_if_t<
388  !std::is_copy_constructible<typename unpack_container<T>::type>::value,
389  T &>
390  construct_element(std::map<std::thread::id, T> &data,
391  const std::thread::id & id,
392  const std::shared_ptr<const T> &)
393  {
394  return data[id];
395  }
396  } // namespace internal
397 # endif
398 
399 
400  template <typename T>
401  inline T &
402  ThreadLocalStorage<T>::get(bool &exists)
403  {
404  const std::thread::id my_id = std::this_thread::get_id();
405 
406  // Note that std::map<..>::emplace guarantees that no iterators or
407  // references to stored objects are invalidated. We thus only have to
408  // ensure that we do not perform a lookup while writing, and that we
409  // do not write concurrently. This is precisely the "reader-writer
410  // lock" paradigm supported by C++14 by means of the std::shared_lock
411  // and the std::unique_lock.
412 
413  {
414  // Take a shared ("reader") lock for lookup and record the fact
415  // whether we could find an entry in the boolean exists.
416  std::shared_lock<decltype(insertion_mutex)> lock(insertion_mutex);
417 
418  const auto it = data.find(my_id);
419  if (it != data.end())
420  {
421  exists = true;
422  return it->second;
423  }
424  else
425  {
426  exists = false;
427  }
428  }
429 
430  {
431  // Take a unique ("writer") lock for manipulating the std::map. This
432  // lock ensures that no other threat does a lookup at the same time.
433  std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
434 
435  return internal::construct_element(data, my_id, exemplar);
436  }
437  }
438 
439 
440  template <typename T>
441  inline T &
443  {
444  bool exists;
445  return get(exists);
446  }
447 
448 
449  template <typename T>
451  {
452  return get();
453  }
454 
455 
456  template <typename T>
457  inline ThreadLocalStorage<T> &
459  {
460  get() = t;
461  return *this;
462  }
463 
464 
465  template <typename T>
466  inline ThreadLocalStorage<T> &
468  {
469  get() = std::forward<T>(t);
470  return *this;
471  }
472 
473 
474  template <typename T>
475  inline void
477  {
478  std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
479  data.clear();
480  }
481 } // namespace Threads
482 
483 # endif // DOXYGEN
484 
485 //---------------------------------------------------------------------------
487 // end of #ifndef dealii_thread_local_storage_h
488 #endif
489 //---------------------------------------------------------------------------
std::map< std::thread::id, T > data
A class that provides a separate storage location on each thread that accesses the object...
std::shared_timed_mutex insertion_mutex
std::shared_ptr< const T > exemplar
static const char T
ThreadLocalStorage & operator=(const ThreadLocalStorage &t)
#define DEAL_II_NAMESPACE_CLOSE
Definition: config.h:363
#define DEAL_II_NAMESPACE_OPEN
Definition: config.h:362