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