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