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