deal.II version GIT relicensing-6834-g5b78e6bcdf 2026-10-01 11:20:01+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\}}\)
Loading...
Searching...
No Matches
thread_local_storage.h
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2011 - 2024 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13#ifndef dealii_thread_local_storage_h
14# define dealii_thread_local_storage_h
15
16
17# include <deal.II/base/config.h>
18
20
21# include <list>
22# include <map>
23# include <memory>
24# include <mutex>
25# include <optional>
26# include <shared_mutex>
27# include <thread>
28# include <vector>
29
31
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_v<
106 typename internal::unpack_container<T>::type> ||
107 std::is_default_constructible_v<T>,
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
191 std::optional<T>
192 get_for_thread(const std::thread::id &id) const;
193
200 operator T &();
201
215 operator=(const T &t);
216
231 operator=(T &&t);
232
252 void
254
255 private:
259 std::map<std::thread::id, T> data;
260
269 mutable std::shared_mutex insertion_mutex;
270
274 std::shared_ptr<const T> exemplar;
275 };
276} // namespace Threads
281# ifndef DOXYGEN
282namespace Threads
283{
284 // ----------------- inline and template functions --------------------------
285
286
287 template <typename T>
288 ThreadLocalStorage<T>::ThreadLocalStorage(const ThreadLocalStorage<T> &t)
289 : exemplar(t.exemplar)
290 {
291 // Raise a reader lock while we are populating our own data in order to
292 // avoid copying over an invalid state.
293 std::shared_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
294 data = t.data;
295 }
296
297
298
299 template <typename T>
300 ThreadLocalStorage<T>::ThreadLocalStorage(ThreadLocalStorage<T> &&t) noexcept
301 : exemplar(std::move(t.exemplar))
302 {
303 // We are nice and raise the writer lock before copying over internal
304 // data structures from the argument.
305 //
306 // The point is a bit moot, though: Users of ThreadLocalStorage
307 // typically obtain their thread's thread-local object through the
308 // get() function. That function also acquires the lock, but
309 // whether or not we do that here really doesn't make any
310 // difference in terms of correctness: If another thread manages
311 // to call get() just before we get here, then the result of that
312 // get() function immediately becomes invalid; if it manages to
313 // call get() at the same time as this function if there were no
314 // locking here, it might access undefined state; and if it
315 // manages to call get() just after we moved away the state --
316 // well, then it just got lucky to escape the race condition, but
317 // the race condition is still there.
318 //
319 // On the other hand, there is no harm in doing at least
320 // conceptually the right thing, so ask for that lock:
321 std::unique_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
322 data = std::move(t.data);
323 }
324
325
326
327 template <typename T>
329 : exemplar(std::make_shared<const T>(t))
330 {}
331
332
333
334 template <typename T>
336 : exemplar(std::make_shared<T>(std::forward<T>(t)))
337 {}
338
339
340
341 template <typename T>
342 inline ThreadLocalStorage<T> &
343 ThreadLocalStorage<T>::operator=(const ThreadLocalStorage<T> &t)
344 {
345 // We need to raise the reader lock of the argument and our writer lock
346 // while copying internal data structures.
347 std::shared_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
348 std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
349
350 data = t.data;
351 exemplar = t.exemplar;
352
353 return *this;
354 }
355
356
357
358 template <typename T>
359 inline ThreadLocalStorage<T> &
360 ThreadLocalStorage<T>::operator=(ThreadLocalStorage<T> &&t) noexcept
361 {
362 // We need to raise the writer lock of the argument (because we're
363 // moving information *away* from that object) and the writer lock
364 // of our object while copying internal data structures.
365 //
366 // That said, the same issue with acquiring the source lock as
367 // with the move constructor above applies here as well.
368 std::unique_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
369 std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
370
371 data = std::move(t.data);
372 exemplar = std::move(t.exemplar);
373
374 return *this;
375 }
376
377
378# ifndef DOXYGEN
379 namespace internal
380 {
381 /*
382 * We have to make sure not to call "data.emplace(id, *exemplar)" if
383 * the corresponding element is not copy constructible. We use some
384 * SFINAE magic to work around the fact that C++14 does not have
385 * "if constexpr".
386 */
387 template <typename T>
388 std::enable_if_t<
389 std::is_copy_constructible_v<typename unpack_container<T>::type>,
390 T &>
391 construct_element(std::map<std::thread::id, T> &data,
392 const std::thread::id &id,
393 const std::shared_ptr<const T> &exemplar)
394 {
395 if (exemplar)
396 {
397 const auto it = data.emplace(id, *exemplar).first;
398 return it->second;
399 }
400 return data[id];
401 }
402
403 template <typename T>
404 std::enable_if_t<
405 !std::is_copy_constructible_v<typename unpack_container<T>::type>,
406 T &>
407 construct_element(std::map<std::thread::id, T> &data,
408 const std::thread::id &id,
409 const std::shared_ptr<const T> &)
410 {
411 return data[id];
412 }
413 } // namespace internal
414# endif
415
416
417 template <typename T>
418 inline T &
419 ThreadLocalStorage<T>::get(bool &exists)
420 {
421 const std::thread::id my_id = std::this_thread::get_id();
422
423 // Note that std::map<..>::emplace guarantees that no iterators or
424 // references to stored objects are invalidated. We thus only have to
425 // ensure that we do not perform a lookup while writing, and that we
426 // do not write concurrently. This is precisely the "reader-writer
427 // lock" paradigm supported by C++14 by means of the std::shared_lock
428 // and the std::unique_lock.
429
430 {
431 // Take a shared ("reader") lock for lookup and record the fact
432 // whether we could find an entry in the boolean exists.
433 std::shared_lock<decltype(insertion_mutex)> lock(insertion_mutex);
434
435 const auto it = data.find(my_id);
436 if (it != data.end())
437 {
438 exists = true;
439 return it->second;
440 }
441 else
442 {
443 exists = false;
444 }
445 }
446
447 {
448 // Take a unique ("writer") lock for manipulating the std::map. This
449 // lock ensures that no other thread does a lookup at the same time.
450 std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
451
452 return internal::construct_element(data, my_id, exemplar);
453 }
454 }
455
456
457 template <typename T>
458 std::optional<T>
459 ThreadLocalStorage<T>::get_for_thread(const std::thread::id &id) const
460 {
461 // Take a shared ("reader") lock for lookup:
462 std::shared_lock<decltype(insertion_mutex)> lock(insertion_mutex);
463
464 // Then see whether we can find the indicated object; if so, copy it,
465 // otherwise return an empty std::optional.
466 const auto it = data.find(id);
467 if (it != data.end())
468 return it->second;
469 else
470 return {};
471 }
472
473
474 template <typename T>
475 inline T &
477 {
478 bool exists;
479 return get(exists);
480 }
481
482
483 template <typename T>
484 inline ThreadLocalStorage<T>::operator T &()
485 {
486 return get();
487 }
488
489
490 template <typename T>
491 inline ThreadLocalStorage<T> &
493 {
494 get() = t;
495 return *this;
496 }
497
498
499 template <typename T>
500 inline ThreadLocalStorage<T> &
502 {
503 get() = std::forward<T>(t);
504 return *this;
505 }
506
507
508 template <typename T>
509 inline void
511 {
512 std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
513 data.clear();
514 }
515} // namespace Threads
516
517# endif // DOXYGEN
518
519//---------------------------------------------------------------------------
521// end of #ifndef dealii_thread_local_storage_h
522#endif
523//---------------------------------------------------------------------------
A class that provides a separate storage location on each thread that accesses the object.
std::optional< T > get_for_thread(const std::thread::id &id) const
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:38
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
std::vector< index_type > data
Definition mpi.cc:734
constexpr char T
*  *  *  *  std::vector< Number > ThermoPlasticMaterial< dim, ViscoplasticYieldLaw, Number >::get_state_parameters   const
STL namespace.