From 86a28ba7b0f9d5a1568823b63f67f364f7929f6e Mon Sep 17 00:00:00 2001 From: Matthias Maier Date: Wed, 20 May 2020 00:40:19 -0500 Subject: [PATCH] ThreadLocalStorage: Reimplement using STL C++11 functionality --- include/deal.II/base/thread_local_storage.h | 143 +++++++++++--------- source/base/logstream.cc | 2 + 2 files changed, 78 insertions(+), 67 deletions(-) diff --git a/include/deal.II/base/thread_local_storage.h b/include/deal.II/base/thread_local_storage.h index 05f61f2e7d..5483f957ae 100644 --- a/include/deal.II/base/thread_local_storage.h +++ b/include/deal.II/base/thread_local_storage.h @@ -19,12 +19,8 @@ # include -DEAL_II_DISABLE_EXTRA_DIAGNOSTICS -# ifdef DEAL_II_WITH_THREADS -# include -# endif -DEAL_II_ENABLE_EXTRA_DIAGNOSTICS - +# include +# include DEAL_II_NAMESPACE_OPEN @@ -44,12 +40,6 @@ namespace Threads * result in race conditions in multithreaded programs since no other thread * than the current one can ever access it. * - * The class builds on the Threading Building Blocks's - * tbb::enumerable_thread_specific class but wraps it in such a way that - * this class can also be used when deal.II is configured not to use threads - * at all -- in that case, this class simply stores a single copy of an - * object of type T. - * *

Construction and destruction

* * Objects of this class can either be default constructed or by providing @@ -71,12 +61,17 @@ namespace Threads template class ThreadLocalStorage { + static_assert( + std::is_trivially_copyable::value || + std::is_copy_constructible::value, + "The stored type must be either copyable, or default constructible"); + public: /** * Default constructor. Initialize each thread local object using its * default constructor. */ - ThreadLocalStorage() = default; + ThreadLocalStorage(); /** * A kind of copy constructor. Initialize each thread local object by @@ -88,7 +83,7 @@ namespace Threads * Copy constructor. Initialize each thread local object with the * corresponding object of the given object. */ - ThreadLocalStorage(const ThreadLocalStorage &t); + ThreadLocalStorage(const ThreadLocalStorage &t) = default; /** * Return a reference to the data stored by this object for the current @@ -158,29 +153,25 @@ namespace Threads void clear(); + private: /** - * Return a reference to the internal Threading Building Blocks - * implementation. This function is really only useful if deal.II has been - * configured with multithreading and has no useful purpose otherwise. + * The data element we store. */ -# ifdef DEAL_II_WITH_THREADS - tbb::enumerable_thread_specific & -# else - T & -# endif - get_implementation(); + std::map data; - private: -# ifdef DEAL_II_WITH_THREADS /** - * The data element we store. If we support threads, then this object will - * be of a type that provides a separate object for each thread. - * Otherwise, it is simply a single object of type T. + * A mutex to guard insertion into the data object. */ - tbb::enumerable_thread_specific data; +# ifdef DEAL_II_HAVE_CXX17 + std::shared_mutex insertion_mutex; # else - T data; + std::shared_timed_mutex insertion_mutex; # endif + + /** + * An exemplar for creating a new (thread specific) copy. + */ + std::shared_ptr exemplar; }; } // namespace Threads /** @@ -193,40 +184,75 @@ namespace Threads // ----------------- inline and template functions -------------------------- template - inline ThreadLocalStorage::ThreadLocalStorage(const T &t) - : data(t) + inline ThreadLocalStorage::ThreadLocalStorage() {} template - inline ThreadLocalStorage::ThreadLocalStorage( - const ThreadLocalStorage &t) - : data(t) - {} + inline ThreadLocalStorage::ThreadLocalStorage(const T &t) + { + exemplar = std::make_shared(t); + } template inline T & - ThreadLocalStorage::get() + ThreadLocalStorage::get(bool &exists) { -# ifdef DEAL_II_WITH_THREADS - return data.local(); -# else - return data; -# endif + const std::thread::id my_id = std::this_thread::get_id(); + + // Note that std::map<..>::emplace guarantees that no iterators or + // references to stored objects are invalidated. We thus only have to + // ensure that we do not performa a lookup while writing, and that we + // do not write concurrently. This is precisely the "reader-writer + // lock" paradigm supported by C++14 by means of the std::shared_lock + // and th std::unique_lock + + { + // Take a shared ("reader") lock for lookup and record the fact + // whether we could find an entry in the boolean exists. + std::shared_lock lock(insertion_mutex); + const auto it = data.find(my_id); + if (it != data.end()) + { + exists = true; + return it->second; + } + else + { + exists = false; + } + } + + { + // Take a unique ("writer") lock for manipulating the std::map. This + // lock ensures that no other threat does a lookup at the same time. + // + std::unique_lock lock(insertion_mutex); + + if constexpr (std::is_trivially_copyable::value) + if (exemplar) + { + const auto it = + data.emplace(std::make_pair(my_id, *exemplar)).first; + return it->second; + } + + if constexpr (std::is_default_constructible::value) + { + const auto it = data.emplace(std::make_pair(my_id, T())).first; + return it->second; + } + } } template inline T & - ThreadLocalStorage::get(bool &exists) + ThreadLocalStorage::get() { -# ifdef DEAL_II_WITH_THREADS - return data.local(exists); -# else - exists = true; - return data; -# endif + bool exists; + return get(exists); } @@ -246,29 +272,12 @@ namespace Threads } - template - inline -# ifdef DEAL_II_WITH_THREADS - tbb::enumerable_thread_specific & -# else - T & -# endif - ThreadLocalStorage::get_implementation() - { - return data; - } - - - template inline void ThreadLocalStorage::clear() { -# ifdef DEAL_II_WITH_THREADS + std::unique_lock lock(insertion_mutex); data.clear(); -# else - data = T{}; -# endif } } // namespace Threads diff --git a/source/base/logstream.cc b/source/base/logstream.cc index 69e619847d..785259e26f 100644 --- a/source/base/logstream.cc +++ b/source/base/logstream.cc @@ -385,6 +385,7 @@ LogStream::get_prefixes() const bool exists = false; std::stack &local_prefixes = prefixes.get(exists); +# if 0 // FIXME // If this is a new locally stored stack, copy the "blessed" prefixes // from the initial thread that created logstream. if (!exists) @@ -402,6 +403,7 @@ LogStream::get_prefixes() const local_prefixes = *first_elem; } } +# endif return local_prefixes; -- 2.39.5