Eigen  5.0.1
 
Loading...
Searching...
No Matches
ThreadLocal.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2016 Benoit Steiner <benoit.steiner.goog@gmail.com>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11#ifndef EIGEN_THREADPOOL_THREAD_LOCAL_H
12#define EIGEN_THREADPOOL_THREAD_LOCAL_H
13
14#ifdef EIGEN_AVOID_THREAD_LOCAL
15
16#ifdef EIGEN_THREAD_LOCAL
17#undef EIGEN_THREAD_LOCAL
18#endif
19
20#else
21
22#define EIGEN_THREAD_LOCAL static thread_local
23
24// Disable TLS for Apple builds with deployment targets that do not support it.
25#if defined(__APPLE__)
26// Included for TARGET_OS_IPHONE, __IPHONE_OS_VERSION_MIN_REQUIRED,
27// __IPHONE_9_0.
28#include <Availability.h>
29#include <TargetConditionals.h>
30#endif
31// Checks whether the `thread_local` storage duration specifier is supported.
32#if EIGEN_COMP_CLANGAPPLE && TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
33// Notes: `thread_local` is not supported when targeting iOS versions before 9.0.
34#undef EIGEN_THREAD_LOCAL
35#endif
36
37#endif // EIGEN_AVOID_THREAD_LOCAL
38
39// IWYU pragma: private
40#include "./InternalHeaderCheck.h"
41
42namespace Eigen {
43
44namespace internal {
45template <typename T>
46struct ThreadLocalNoOpInitialize {
47 void operator()(T&) const {}
48};
49
50template <typename T>
51struct ThreadLocalNoOpRelease {
52 void operator()(T&) const {}
53};
54
55} // namespace internal
56
57// Thread local container for elements of type T, that does not use thread local
58// storage. As long as the number of unique threads accessing this storage
59// is smaller than `capacity_`, it is lock-free and wait-free. Otherwise it will
60// use a mutex for synchronization.
61//
62// Type `T` has to be default constructible, and by default each thread will get
63// a default constructed value. It is possible to specify custom `initialize`
64// callable, that will be called lazily from each thread accessing this object,
65// and will be passed a default initialized object of type `T`. Also it's
66// possible to pass a custom `release` callable, that will be invoked before
67// calling ~T().
68//
69// Example:
70//
71// struct Counter {
72// int value = 0;
73// }
74//
75// Eigen::ThreadLocal<Counter> counter(10);
76//
77// // Each thread will have access to its own counter object.
78// Counter& cnt = counter.local();
79// cnt++;
80//
81// WARNING: Eigen::ThreadLocal uses the OS-specific value returned by
82// std::this_thread::get_id() to identify threads. This value is not guaranteed
83// to be unique except for the life of the thread. A newly created thread may
84// get an OS-specific ID equal to that of an already destroyed thread.
85//
86// Somewhat similar to TBB thread local storage, with similar restrictions:
87// https://www.threadingbuildingblocks.org/docs/help/reference/thread_local_storage/enumerable_thread_specific_cls.html
88//
89template <typename T, typename Initialize = internal::ThreadLocalNoOpInitialize<T>,
90 typename Release = internal::ThreadLocalNoOpRelease<T>>
91class ThreadLocal {
92 // We preallocate default constructed elements in MaxSizedVector.
93 static_assert(std::is_default_constructible<T>::value, "ThreadLocal data type must be default constructible");
94
95 public:
96 explicit ThreadLocal(int capacity)
97 : ThreadLocal(capacity, internal::ThreadLocalNoOpInitialize<T>(), internal::ThreadLocalNoOpRelease<T>()) {}
98
99 ThreadLocal(int capacity, Initialize initialize)
100 : ThreadLocal(capacity, std::move(initialize), internal::ThreadLocalNoOpRelease<T>()) {}
101
102 ThreadLocal(int capacity, Initialize initialize, Release release)
103 : initialize_(std::move(initialize)),
104 release_(std::move(release)),
105 capacity_(capacity),
106 data_(capacity_),
107 ptr_(capacity_),
108 filled_records_(0) {
109 eigen_assert(capacity_ >= 0);
110 data_.resize(capacity_);
111 for (int i = 0; i < capacity_; ++i) {
112 ptr_.emplace_back(nullptr);
113 }
114 }
115
116 T& local() {
117 std::thread::id this_thread = std::this_thread::get_id();
118 if (capacity_ == 0) return SpilledLocal(this_thread);
119
120 std::size_t h = std::hash<std::thread::id>()(this_thread);
121 const int start_idx = h % capacity_;
122
123 // NOTE: From the definition of `std::this_thread::get_id()` it is
124 // guaranteed that we never can have concurrent insertions with the same key
125 // to our hash-map like data structure. If we didn't find an element during
126 // the initial traversal, it's guaranteed that no one else could have
127 // inserted it while we are in this function. This allows to massively
128 // simplify our lock-free insert-only hash map.
129
130 // Check if we already have an element for `this_thread`.
131 int idx = start_idx;
132 while (ptr_[idx].load() != nullptr) {
133 ThreadIdAndValue& record = *(ptr_[idx].load());
134 if (record.thread_id == this_thread) return record.value;
135
136 idx += 1;
137 if (idx >= capacity_) idx -= capacity_;
138 if (idx == start_idx) break;
139 }
140
141 // If we are here, it means that we found an insertion point in lookup
142 // table at `idx`, or we did a full traversal and table is full.
143
144 // If lock-free storage is full, fallback on mutex.
145 if (filled_records_.load() >= capacity_) return SpilledLocal(this_thread);
146
147 // We double check that we still have space to insert an element into a lock
148 // free storage. If old value in `filled_records_` is larger than the
149 // records capacity, it means that some other thread added an element while
150 // we were traversing lookup table.
151 int insertion_index = filled_records_.fetch_add(1, std::memory_order_relaxed);
152 if (insertion_index >= capacity_) return SpilledLocal(this_thread);
153
154 // At this point it's guaranteed that we can access to
155 // data_[insertion_index] without a data race.
156 data_[insertion_index].thread_id = this_thread;
157 initialize_(data_[insertion_index].value);
158
159 // That's the pointer we'll put into the lookup table.
160 ThreadIdAndValue* inserted = &data_[insertion_index];
161
162 // We'll use nullptr pointer to ThreadIdAndValue in a compare-and-swap loop.
163 ThreadIdAndValue* empty = nullptr;
164
165 // Now we have to find an insertion point into the lookup table. We start
166 // from the `idx` that was identified as an insertion point above, it's
167 // guaranteed that we will have an empty record somewhere in a lookup table
168 // (because we created a record in the `data_`).
169 const int insertion_idx = idx;
170
171 do {
172 // Always start search from the original insertion candidate.
173 idx = insertion_idx;
174 while (ptr_[idx].load() != nullptr) {
175 idx += 1;
176 if (idx >= capacity_) idx -= capacity_;
177 // If we did a full loop, it means that we don't have any free entries
178 // in the lookup table, and this means that something is terribly wrong.
179 eigen_assert(idx != insertion_idx);
180 }
181 // Atomic CAS of the pointer guarantees that any other thread, that will
182 // follow this pointer will see all the mutations in the `data_`.
183 } while (!ptr_[idx].compare_exchange_weak(empty, inserted));
184
185 return inserted->value;
186 }
187
188 // WARN: It's not thread safe to call it concurrently with `local()`.
189 void ForEach(std::function<void(std::thread::id, T&)> f) {
190 // Reading directly from `data_` is unsafe, because only CAS to the
191 // record in `ptr_` makes all changes visible to other threads.
192 for (auto& ptr : ptr_) {
193 ThreadIdAndValue* record = ptr.load();
194 if (record == nullptr) continue;
195 f(record->thread_id, record->value);
196 }
197
198 // We did not spill into the map based storage.
199 if (filled_records_.load(std::memory_order_relaxed) < capacity_) return;
200
201 // Adds a happens before edge from the last call to SpilledLocal().
202 EIGEN_MUTEX_LOCK lock(mu_);
203 for (auto& kv : per_thread_map_) {
204 f(kv.first, kv.second);
205 }
206 }
207
208 // WARN: It's not thread safe to call it concurrently with `local()`.
209 ~ThreadLocal() {
210 // Reading directly from `data_` is unsafe, because only CAS to the record
211 // in `ptr_` makes all changes visible to other threads.
212 for (auto& ptr : ptr_) {
213 ThreadIdAndValue* record = ptr.load();
214 if (record == nullptr) continue;
215 release_(record->value);
216 }
217
218 // We did not spill into the map based storage.
219 if (filled_records_.load(std::memory_order_relaxed) < capacity_) return;
220
221 // Adds a happens before edge from the last call to SpilledLocal().
222 EIGEN_MUTEX_LOCK lock(mu_);
223 for (auto& kv : per_thread_map_) {
224 release_(kv.second);
225 }
226 }
227
228 private:
229 struct ThreadIdAndValue {
230 std::thread::id thread_id;
231 T value;
232 };
233
234 // Use unordered map guarded by a mutex when lock free storage is full.
235 T& SpilledLocal(std::thread::id this_thread) {
236 EIGEN_MUTEX_LOCK lock(mu_);
237
238 auto it = per_thread_map_.find(this_thread);
239 if (it == per_thread_map_.end()) {
240 auto result = per_thread_map_.emplace(this_thread, T());
241 eigen_assert(result.second);
242 initialize_((*result.first).second);
243 return (*result.first).second;
244 } else {
245 return it->second;
246 }
247 }
248
249 Initialize initialize_;
250 Release release_;
251 const int capacity_;
252
253 // Storage that backs lock-free lookup table `ptr_`. Records stored in this
254 // storage contiguously starting from index 0.
255 MaxSizeVector<ThreadIdAndValue> data_;
256
257 // Atomic pointers to the data stored in `data_`. Used as a lookup table for
258 // linear probing hash map (https://en.wikipedia.org/wiki/Linear_probing).
259 MaxSizeVector<std::atomic<ThreadIdAndValue*>> ptr_;
260
261 // Number of records stored in the `data_`.
262 std::atomic<int> filled_records_;
263
264 // We fallback on per thread map if lock-free storage is full. In practice
265 // this should never happen, if `capacity_` is a reasonable estimate of the
266 // number of threads running in a system.
267 EIGEN_MUTEX mu_; // Protects per_thread_map_.
268 std::unordered_map<std::thread::id, T> per_thread_map_;
269};
270
271} // namespace Eigen
272
273#endif // EIGEN_THREADPOOL_THREAD_LOCAL_H