Reference documentation for deal.II version GIT 7b2de2f2f9 2023-09-24 11:00: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 - 2022 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_v<T>,
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  mutable std::shared_mutex insertion_mutex;
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>
275  ThreadLocalStorage<T>::ThreadLocalStorage(const ThreadLocalStorage<T> &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>
287  ThreadLocalStorage<T>::ThreadLocalStorage(ThreadLocalStorage<T> &&t) noexcept
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> &
330  ThreadLocalStorage<T>::operator=(const ThreadLocalStorage<T> &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  exemplar = t.exemplar;
339 
340  return *this;
341  }
342 
343 
344 
345  template <typename T>
346  inline ThreadLocalStorage<T> &
347  ThreadLocalStorage<T>::operator=(ThreadLocalStorage<T> &&t) noexcept
348  {
349  // We need to raise the writer lock of the argument (because we're
350  // moving information *away* from that object) and the writer lock
351  // of our object while copying internal data structures.
352  //
353  // That said, the same issue with acquiring the source lock as
354  // with the move constructor above applies here as well.
355  std::unique_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
356  std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
357 
358  data = std::move(t.data);
359  exemplar = std::move(t.exemplar);
360 
361  return *this;
362  }
363 
364 
365 # ifndef DOXYGEN
366  namespace internal
367  {
368  /*
369  * We have to make sure not to call "data.emplace(id, *exemplar)" if
370  * the corresponding element is not copy constructible. We use some
371  * SFINAE magic to work around the fact that C++14 does not have
372  * "if constexpr".
373  */
374  template <typename T>
375  std::enable_if_t<
376  std::is_copy_constructible_v<typename unpack_container<T>::type>,
377  T &>
378  construct_element(std::map<std::thread::id, T> &data,
379  const std::thread::id &id,
380  const std::shared_ptr<const T> &exemplar)
381  {
382  if (exemplar)
383  {
384  const auto it = data.emplace(id, *exemplar).first;
385  return it->second;
386  }
387  return data[id];
388  }
389 
390  template <typename T>
391  std::enable_if_t<
392  !std::is_copy_constructible_v<typename unpack_container<T>::type>,
393  T &>
394  construct_element(std::map<std::thread::id, T> &data,
395  const std::thread::id &id,
396  const std::shared_ptr<const T> &)
397  {
398  return data[id];
399  }
400  } // namespace internal
401 # endif
402 
403 
404  template <typename T>
405  inline T &
406  ThreadLocalStorage<T>::get(bool &exists)
407  {
408  const std::thread::id my_id = std::this_thread::get_id();
409 
410  // Note that std::map<..>::emplace guarantees that no iterators or
411  // references to stored objects are invalidated. We thus only have to
412  // ensure that we do not perform a lookup while writing, and that we
413  // do not write concurrently. This is precisely the "reader-writer
414  // lock" paradigm supported by C++14 by means of the std::shared_lock
415  // and the std::unique_lock.
416 
417  {
418  // Take a shared ("reader") lock for lookup and record the fact
419  // whether we could find an entry in the boolean exists.
420  std::shared_lock<decltype(insertion_mutex)> lock(insertion_mutex);
421 
422  const auto it = data.find(my_id);
423  if (it != data.end())
424  {
425  exists = true;
426  return it->second;
427  }
428  else
429  {
430  exists = false;
431  }
432  }
433 
434  {
435  // Take a unique ("writer") lock for manipulating the std::map. This
436  // lock ensures that no other thread does a lookup at the same time.
437  std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
438 
439  return internal::construct_element(data, my_id, exemplar);
440  }
441  }
442 
443 
444  template <typename T>
445  inline T &
447  {
448  bool exists;
449  return get(exists);
450  }
451 
452 
453  template <typename T>
454  inline ThreadLocalStorage<T>::operator T &()
455  {
456  return get();
457  }
458 
459 
460  template <typename T>
461  inline ThreadLocalStorage<T> &
463  {
464  get() = t;
465  return *this;
466  }
467 
468 
469  template <typename T>
470  inline ThreadLocalStorage<T> &
472  {
473  get() = std::forward<T>(t);
474  return *this;
475  }
476 
477 
478  template <typename T>
479  inline void
481  {
482  std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
483  data.clear();
484  }
485 } // namespace Threads
486 
487 # endif // DOXYGEN
488 
489 //---------------------------------------------------------------------------
491 // end of #ifndef dealii_thread_local_storage_h
492 #endif
493 //---------------------------------------------------------------------------
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:477
#define DEAL_II_NAMESPACE_CLOSE
Definition: config.h:478
static const char T
Definition: mutex.h:32