Reference documentation for deal.II version 9.5.0
\(\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 - 2022 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
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<
108 typename internal::unpack_container<T>::type>::value ||
109 std::is_default_constructible<T>::value,
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
185 operator T &();
186
200 operator=(const T &t);
201
216 operator=(T &&t);
217
237 void
239
240 private:
244 std::map<std::thread::id, T> data;
245
254# ifdef DEAL_II_HAVE_CXX17
255 mutable std::shared_mutex insertion_mutex;
256# else
257 mutable std::shared_timed_mutex insertion_mutex;
258# endif
259
263 std::shared_ptr<const T> exemplar;
264
265 friend class ::LogStream;
266 };
267} // namespace Threads
272# ifndef DOXYGEN
273namespace Threads
274{
275 // ----------------- inline and template functions --------------------------
276
277
278 template <typename T>
279 ThreadLocalStorage<T>::ThreadLocalStorage(const ThreadLocalStorage<T> &t)
280 : exemplar(t.exemplar)
281 {
282 // Raise a reader lock while we are populating our own data in order to
283 // avoid copying over an invalid state.
284 std::shared_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
285 data = t.data;
286 }
287
288
289
290 template <typename T>
291 ThreadLocalStorage<T>::ThreadLocalStorage(ThreadLocalStorage<T> &&t) noexcept
292 : exemplar(std::move(t.exemplar))
293 {
294 // We are nice and raise the writer lock before copying over internal
295 // data structures from the argument.
296 //
297 // The point is a bit moot, though: Users of ThreadLocalStorage
298 // typically obtain their thread's thread-local object through the
299 // get() function. That function also acquires the lock, but
300 // whether or not we do that here really doesn't make any
301 // difference in terms of correctness: If another thread manages
302 // to call get() just before we get here, then the result of that
303 // get() function immediately becomes invalid; if it manages to
304 // call get() at the same time as this function if there were no
305 // locking here, it might access undefined state; and if it
306 // manages to call get() just after we moved away the state --
307 // well, then it just got lucky to escape the race condition, but
308 // the race condition is still there.
309 //
310 // On the other hand, there is no harm in doing at least
311 // conceptually the right thing, so ask for that lock:
312 std::unique_lock<decltype(insertion_mutex)> lock(t.insertion_mutex);
313 data = std::move(t.data);
314 }
315
316
317
318 template <typename T>
320 : exemplar(std::make_shared<const T>(t))
321 {}
322
323
324
325 template <typename T>
327 : exemplar(std::make_shared<T>(std::forward<T>(t)))
328 {}
329
330
331
332 template <typename T>
333 inline ThreadLocalStorage<T> &
334 ThreadLocalStorage<T>::operator=(const ThreadLocalStorage<T> &t)
335 {
336 // We need to raise the reader lock of the argument and our writer lock
337 // while copying internal data structures.
338 std::shared_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
339 std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
340
341 data = t.data;
342 exemplar = t.exemplar;
343
344 return *this;
345 }
346
347
348
349 template <typename T>
350 inline ThreadLocalStorage<T> &
351 ThreadLocalStorage<T>::operator=(ThreadLocalStorage<T> &&t) noexcept
352 {
353 // We need to raise the writer lock of the argument (because we're
354 // moving information *away* from that object) and the writer lock
355 // of our object while copying internal data structures.
356 //
357 // That said, the same issue with acquiring the source lock as
358 // with the move constructor above applies here as well.
359 std::unique_lock<decltype(insertion_mutex)> reader_lock(t.insertion_mutex);
360 std::unique_lock<decltype(insertion_mutex)> writer_lock(insertion_mutex);
361
362 data = std::move(t.data);
363 exemplar = std::move(t.exemplar);
364
365 return *this;
366 }
367
368
369# ifndef DOXYGEN
370 namespace internal
371 {
372 /*
373 * We have to make sure not to call "data.emplace(id, *exemplar)" if
374 * the corresponding element is not copy constructible. We use some
375 * SFINAE magic to work around the fact that C++14 does not have
376 * "if constexpr".
377 */
378 template <typename T>
379 std::enable_if_t<
380 std::is_copy_constructible<typename unpack_container<T>::type>::value,
381 T &>
382 construct_element(std::map<std::thread::id, T> & data,
383 const std::thread::id & id,
384 const std::shared_ptr<const T> &exemplar)
385 {
386 if (exemplar)
387 {
388 const auto it = data.emplace(id, *exemplar).first;
389 return it->second;
390 }
391 return data[id];
392 }
393
394 template <typename T>
395 std::enable_if_t<
396 !std::is_copy_constructible<typename unpack_container<T>::type>::value,
397 T &>
398 construct_element(std::map<std::thread::id, T> &data,
399 const std::thread::id & id,
400 const std::shared_ptr<const T> &)
401 {
402 return data[id];
403 }
404 } // namespace internal
405# endif
406
407
408 template <typename T>
409 inline T &
410 ThreadLocalStorage<T>::get(bool &exists)
411 {
412 const std::thread::id my_id = std::this_thread::get_id();
413
414 // Note that std::map<..>::emplace guarantees that no iterators or
415 // references to stored objects are invalidated. We thus only have to
416 // ensure that we do not perform a lookup while writing, and that we
417 // do not write concurrently. This is precisely the "reader-writer
418 // lock" paradigm supported by C++14 by means of the std::shared_lock
419 // and the std::unique_lock.
420
421 {
422 // Take a shared ("reader") lock for lookup and record the fact
423 // whether we could find an entry in the boolean exists.
424 std::shared_lock<decltype(insertion_mutex)> lock(insertion_mutex);
425
426 const auto it = data.find(my_id);
427 if (it != data.end())
428 {
429 exists = true;
430 return it->second;
431 }
432 else
433 {
434 exists = false;
435 }
436 }
437
438 {
439 // Take a unique ("writer") lock for manipulating the std::map. This
440 // lock ensures that no other thread does a lookup at the same time.
441 std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
442
443 return internal::construct_element(data, my_id, exemplar);
444 }
445 }
446
447
448 template <typename T>
449 inline T &
451 {
452 bool exists;
453 return get(exists);
454 }
455
456
457 template <typename T>
458 inline ThreadLocalStorage<T>::operator T &()
459 {
460 return get();
461 }
462
463
464 template <typename T>
465 inline ThreadLocalStorage<T> &
467 {
468 get() = t;
469 return *this;
470 }
471
472
473 template <typename T>
474 inline ThreadLocalStorage<T> &
476 {
477 get() = std::forward<T>(t);
478 return *this;
479 }
480
481
482 template <typename T>
483 inline void
485 {
486 std::unique_lock<decltype(insertion_mutex)> lock(insertion_mutex);
487 data.clear();
488 }
489} // namespace Threads
490
491# endif // DOXYGEN
492
493//---------------------------------------------------------------------------
495// end of #ifndef dealii_thread_local_storage_h
496#endif
497//---------------------------------------------------------------------------
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:472
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:473
STL namespace.