Reference documentation for deal.II version GIT relicensing-224-gc660c0d696 2024-03-28 18:40: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\}}\)
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 - 2023 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 <shared_mutex>
28# include <thread>
29# include <vector>
30
32
38# ifndef DOXYGEN
39class LogStream;
40# endif
41
42namespace Threads
43{
44# ifndef DOXYGEN
45 namespace internal
46 {
47 /*
48 * Workaround: The standard unfortunately has an unfortunate design
49 * "flaw" in the std::is_copy_constructible type trait
50 * when it comes to STL containers and containing non-copyable objects
51 * T. The type trait is true even though any attempted invocation leads
52 * to a compilation error. Work around this issue by unpacking some
53 * commonly used containers:
54 */
55 template <typename T>
56 struct unpack_container
57 {
58 using type = T;
59 };
60
61 template <typename T, typename A>
62 struct unpack_container<std::vector<T, A>>
63 {
64 using type = T;
65 };
66
67 template <typename T, typename A>
68 struct unpack_container<std::list<T, A>>
69 {
70 using type = T;
71 };
72 } // namespace internal
73# endif
74
102 template <typename T>
104 {
105 static_assert(
106 std::is_copy_constructible<
107 typename internal::unpack_container<T>::type>::value ||
108 std::is_default_constructible_v<T>,
109 "The stored type must be either copyable, or default constructible");
110
111 public:
117
122
128
134 explicit ThreadLocalStorage(const T &t);
135
141 explicit ThreadLocalStorage(T &&t);
142
148
154
168 T &
170
175 T &
176 get(bool &exists);
177
184 operator T &();
185
199 operator=(const T &t);
200
215 operator=(T &&t);
216
236 void
238
239 private:
243 std::map<std::thread::id, T> data;
244
253 mutable std::shared_mutex insertion_mutex;
254
258 std::shared_ptr<const T> exemplar;
259
260 friend class ::LogStream;
261 };
262} // namespace Threads
267# ifndef DOXYGEN
268namespace Threads
269{
270 // ----------------- inline and template functions --------------------------
271
272
273 template <typename T>
274 ThreadLocalStorage<T>::ThreadLocalStorage(const ThreadLocalStorage<T> &t)
275 : exemplar(t.exemplar)
276 {
277 // Raise a reader lock while we are populating our own data in order to
278 // avoid copying over an invalid state.
279 std::shared_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
280 data = t.data;
281 }
282
283
284
285 template <typename T>
286 ThreadLocalStorage<T>::ThreadLocalStorage(ThreadLocalStorage<T> &&t) noexcept
287 : exemplar(std::move(t.exemplar))
288 {
289 // We are nice and raise the writer lock before copying over internal
290 // data structures from the argument.
291 //
292 // The point is a bit moot, though: Users of ThreadLocalStorage
293 // typically obtain their thread's thread-local object through the
294 // get() function. That function also acquires the lock, but
295 // whether or not we do that here really doesn't make any
296 // difference in terms of correctness: If another thread manages
297 // to call get() just before we get here, then the result of that
298 // get() function immediately becomes invalid; if it manages to
299 // call get() at the same time as this function if there were no
300 // locking here, it might access undefined state; and if it
301 // manages to call get() just after we moved away the state --
302 // well, then it just got lucky to escape the race condition, but
303 // the race condition is still there.
304 //
305 // On the other hand, there is no harm in doing at least
306 // conceptually the right thing, so ask for that lock:
307 std::unique_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
308 data = std::move(t.data);
309 }
310
311
312
313 template <typename T>
315 : exemplar(std::make_shared<const T>(t))
316 {}
317
318
319
320 template <typename T>
322 : exemplar(std::make_shared<T>(std::forward<T>(t)))
323 {}
324
325
326
327 template <typename T>
328 inline ThreadLocalStorage<T> &
329 ThreadLocalStorage<T>::operator=(const ThreadLocalStorage<T> &t)
330 {
331 // We need to raise the reader lock of the argument and our writer lock
332 // while copying internal data structures.
333 std::shared_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
334 std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
335
336 data = t.data;
337 exemplar = t.exemplar;
338
339 return *this;
340 }
341
342
343
344 template <typename T>
345 inline ThreadLocalStorage<T> &
346 ThreadLocalStorage<T>::operator=(ThreadLocalStorage<T> &&t) noexcept
347 {
348 // We need to raise the writer lock of the argument (because we're
349 // moving information *away* from that object) and the writer lock
350 // of our object while copying internal data structures.
351 //
352 // That said, the same issue with acquiring the source lock as
353 // with the move constructor above applies here as well.
354 std::unique_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
355 std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
356
357 data = std::move(t.data);
358 exemplar = std::move(t.exemplar);
359
360 return *this;
361 }
362
363
364# ifndef DOXYGEN
365 namespace internal
366 {
367 /*
368 * We have to make sure not to call "data.emplace(id, *exemplar)" if
369 * the corresponding element is not copy constructible. We use some
370 * SFINAE magic to work around the fact that C++14 does not have
371 * "if constexpr".
372 */
373 template <typename T>
374 std::enable_if_t<
375 std::is_copy_constructible_v<typename unpack_container<T>::type>,
376 T &>
377 construct_element(std::map<std::thread::id, T> &data,
378 const std::thread::id &id,
379 const std::shared_ptr<const T> &exemplar)
380 {
381 if (exemplar)
382 {
383 const auto it = data.emplace(id, *exemplar).first;
384 return it->second;
385 }
386 return data[id];
387 }
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> &)
396 {
397 return data[id];
398 }
399 } // namespace internal
400# endif
401
402
403 template <typename T>
404 inline T &
405 ThreadLocalStorage<T>::get(bool &exists)
406 {
407 const std::thread::id my_id = std::this_thread::get_id();
408
409 // Note that std::map<..>::emplace guarantees that no iterators or
410 // references to stored objects are invalidated. We thus only have to
411 // ensure that we do not perform a lookup while writing, and that we
412 // do not write concurrently. This is precisely the "reader-writer
413 // lock" paradigm supported by C++14 by means of the std::shared_lock
414 // and the std::unique_lock.
415
416 {
417 // Take a shared ("reader") lock for lookup and record the fact
418 // whether we could find an entry in the boolean exists.
419 std::shared_lock<decltype(insertion_mutex)> lock(insertion_mutex);
420
421 const auto it = data.find(my_id);
422 if (it != data.end())
423 {
424 exists = true;
425 return it->second;
426 }
427 else
428 {
429 exists = false;
430 }
431 }
432
433 {
434 // Take a unique ("writer") lock for manipulating the std::map. This
435 // lock ensures that no other thread does a lookup at the same time.
436 std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
437
438 return internal::construct_element(data, my_id, exemplar);
439 }
440 }
441
442
443 template <typename T>
444 inline T &
446 {
447 bool exists;
448 return get(exists);
449 }
450
451
452 template <typename T>
453 inline ThreadLocalStorage<T>::operator T &()
454 {
455 return get();
456 }
457
458
459 template <typename T>
460 inline ThreadLocalStorage<T> &
462 {
463 get() = t;
464 return *this;
465 }
466
467
468 template <typename T>
469 inline ThreadLocalStorage<T> &
471 {
472 get() = std::forward<T>(t);
473 return *this;
474 }
475
476
477 template <typename T>
478 inline void
480 {
481 std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
482 data.clear();
483 }
484} // namespace Threads
485
486# endif // DOXYGEN
487
488//---------------------------------------------------------------------------
490// end of #ifndef dealii_thread_local_storage_h
491#endif
492//---------------------------------------------------------------------------
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:502
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:503
STL namespace.