Reference documentation for deal.II version 9.4.1
\(\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\}}\)
Loading...
Searching...
No Matches
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
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
38class LogStream;
39# endif
40
41namespace 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:
116
121
127
133 explicit ThreadLocalStorage(const T &t);
134
140 explicit ThreadLocalStorage(T &&t);
141
147
153
167 T &
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
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
271namespace 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 & operator=(const ThreadLocalStorage &t)
ThreadLocalStorage(const ThreadLocalStorage &)
std::map< std::thread::id, T > data
ThreadLocalStorage< T > & operator=(const T &t)
std::shared_ptr< const T > exemplar
ThreadLocalStorage & operator=(ThreadLocalStorage &&t) noexcept
ThreadLocalStorage< T > & operator=(T &&t)
ThreadLocalStorage(ThreadLocalStorage &&t) noexcept
#define DEAL_II_NAMESPACE_OPEN
Definition: config.h:442
#define DEAL_II_NAMESPACE_CLOSE
Definition: config.h:443
STL namespace.