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