Reference documentation for deal.II version Git 4fbb5374f0 2021-01-22 15:02:13 -0500
\(\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  exemplar = t.exemplar;
339 
340  return *this;
341  }
342 
343 
344 
345  template <typename T>
346  inline ThreadLocalStorage<T> &
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  typename std::enable_if_t<
376  std::is_copy_constructible<typename unpack_container<T>::type>::value,
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  typename std::enable_if_t<
392  !std::is_copy_constructible<typename unpack_container<T>::type>::value,
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 threat 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>
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 //---------------------------------------------------------------------------
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:380
#define DEAL_II_NAMESPACE_OPEN
Definition: config.h:379