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