Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
thread_safe_id_map.h
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef OR_TOOLS_MATH_OPT_ELEMENTAL_THREAD_SAFE_ID_MAP_H_
15#define OR_TOOLS_MATH_OPT_ELEMENTAL_THREAD_SAFE_ID_MAP_H_
16
17#include <atomic>
18#include <cstdint>
19#include <memory>
20#include <utility>
21#include <vector>
22
23#include "absl/base/thread_annotations.h"
24#include "absl/container/flat_hash_set.h"
25#include "absl/synchronization/mutex.h"
26#include "absl/types/span.h"
28
30
31// A map from int64_t ids to V, where the ids are created by this map and handed
32// out sequentially.
33//
34// The underlying storage for this map is vector<pair<int64_t, unique_ptr<V>>>.
35// Insertions and deletions from this vector are done lazily whenever any of the
36// functions UpdateXXX() are invoked.
37//
38// At a high level, the purpose of this class is to allow for the thread-safe
39// removal of elements from the map, while have as little overhead as possible
40// when iterating over the elements of the map (`UpdateAndGetAll()`).
41// In particular, in the common case where there is nothing to update,
42// `UpdateAndGetAll()` only incurs the cost of a single atomic read with
43// std::memory_order_relaxed, which is much faster than acquiring a lock on a
44// mutex.
45//
46// This map has pointer stability for values, users can only insert by providing
47// a `unique_ptr<V>`.
48//
49// The functions of this class are mutually thread-safe. However, the functions:
50// * UpdateAndGetAll()
51// * GetAll()
52// * UpdateAndGet()
53// * Get()
54// return pointers that:
55// * can be invalidated other function calls on this class,
56// * are not const and may contain mutable data (if V is mutable).
57// Thus there are some limitations on the use of this class in a concurrent
58// context. Each of these functions above returning pointers documents their
59// invalidation conditions inline. Most importantly, it is safe for a single
60// thread to invoke UpdateAndGetAll(), and then modify the returned V*, with an
61// arbitrary number of concurrent calls to Erase().
62template <typename V>
64 public:
65 ThreadSafeIdMap() = default;
66
67 // Returns all key-value pairs in the map.
68 //
69 // The returned span is invalidated by an Insert() or Erase() followed by a
70 // call to UpdateAndGetAll() or UpdateAndGet().
71 //
72 // Note: the V values returned are mutable. Any concurrent mutations must be
73 // synchronized externally.
74 absl::Span<const std::pair<int64_t, std::unique_ptr<V>>> UpdateAndGetAll();
75
76 // Returns all key-value paris in the map.
77 //
78 // In contrast to UpdateAndGetAll(), this function will always acquire a lock
79 // and copy the data before returning. Thus, this function is slower, but the
80 // values are harder to invalidate. This function is also const, while
81 // UpdateAndGetAll() is not. Last, because this function does not update, it
82 // will not invalidate any pointers returned by other functions on this class.
83 //
84 // For each (id, V*) pair, the V* is
85 // invalidated by either:
86 // * ~ThreadSafeIdMap()
87 // * `Erase(id)` followed by any call to UpdateXXX().
88 //
89 // Note: the V values returned are mutable. Any concurrent mutations must be
90 // synchronized externally.
91 std::vector<std::pair<int64_t, V*>> GetAll() const;
92
93 // Returns the value for this key, or nullptr if this key is not in the map.
94 //
95 // The returned pointer is invalidated by either of:
96 // * ~ThreadSafeIdMap()
97 // * `Erase(id)` followed by any call to UpdateXXX().
98 //
99 // Warning: this does NOT run O(1) time, the complexity is linear in the
100 // number of elements in the map plus the number of pending insertions and
101 // deletions.
102 //
103 // Note: the V value returned is mutable. Any concurrent mutations must be
104 // synchronized externally.
105 V* UpdateAndGet(int64_t id);
106
107 // Returns the value for this key, or nullptr if this key is not in the map.
108 //
109 // The returned pointer is invalidated by any call to `Erase(id)` or
110 // ~ThreadSafeIdMap().
111 //
112 // This function is similar to UpdateAndGet(), but it is const. It can be
113 // slightly slower, but it is also safer to use from a concurrent context, as
114 // it will not invalidate any pointers returned by other functions on this
115 // class.
116 //
117 // Warning: this does NOT run O(1) time, the complexity is linear in the
118 // number of elements in the map plus the number of pending insertions and
119 // deletions.
120 //
121 // Note: the V value returned is mutable. Any concurrent mutations must be
122 // synchronized externally.
123 V* Get(int64_t id) const;
124
125 // Inserts value into the map and returns the assigned key.
126 int64_t Insert(std::unique_ptr<V> value);
127
128 // Erases key from the map, returning true if the key was found in the map.
129 bool Erase(int64_t key);
130
131 // The number of elements in the map.
132 int64_t Size() const;
133
134 private:
135 void ApplyPendingModifications() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
136
137 void UpdateHasPendingModifications() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
138
139 mutable absl::Mutex mutex_;
140 std::atomic<bool> has_pending_modifications_ = false;
141 int64_t next_id_ ABSL_GUARDED_BY(mutex_) = 0;
142
143 std::vector<std::pair<int64_t, std::unique_ptr<V>>> elements_;
144 std::vector<std::pair<int64_t, std::unique_ptr<V>>> pending_inserts_
145 ABSL_GUARDED_BY(mutex_);
146 absl::flat_hash_set<int64_t> pending_deletes_ ABSL_GUARDED_BY(mutex_);
147};
148
150// Template function implementations.
152
153template <typename V>
154absl::Span<const std::pair<int64_t, std::unique_ptr<V>>>
156 if (has_pending_modifications_.load(std::memory_order_relaxed)) {
157 absl::MutexLock lock(&mutex_);
158 ApplyPendingModifications();
159 }
160 return absl::MakeConstSpan(elements_);
161}
162
163template <typename V>
164int64_t ThreadSafeIdMap<V>::Size() const {
165 absl::MutexLock lock(&mutex_);
166 return static_cast<int64_t>(elements_.size() + pending_inserts_.size()) -
167 static_cast<int64_t>(pending_deletes_.size());
168}
169
170template <typename V>
171V* ThreadSafeIdMap<V>::Get(const int64_t id) const {
172 absl::MutexLock lock(&mutex_);
173 if (pending_deletes_.contains(id)) {
174 return nullptr;
175 }
176 for (const auto& [key, value] : pending_inserts_) {
177 if (id == key) {
178 return value.get();
179 }
180 }
181 for (const auto& [key, value] : elements_) {
182 if (id == key) {
183 return value.get();
184 }
185 }
186 return nullptr;
187}
188
189template <typename V>
190std::vector<std::pair<int64_t, V*>> ThreadSafeIdMap<V>::GetAll() const {
191 absl::MutexLock lock(&mutex_);
192 std::vector<std::pair<int64_t, V*>> result;
193 for (const auto& [id, diff] : elements_) {
194 if (!pending_deletes_.contains(id)) {
195 result.push_back({id, diff.get()});
196 }
197 }
198 for (const auto& [id, diff] : pending_inserts_) {
199 if (!pending_deletes_.contains(id)) {
200 result.push_back({id, diff.get()});
201 }
202 }
203 return result;
204}
205
206template <typename V>
207V* ThreadSafeIdMap<V>::UpdateAndGet(const int64_t id) {
208 absl::MutexLock lock(&mutex_);
209 if (has_pending_modifications_.load()) {
210 ApplyPendingModifications();
211 }
212 for (const auto& [key, value] : elements_) {
213 if (id == key) {
214 return value.get();
215 }
216 }
217 return nullptr;
218}
219
220template <typename V>
221int64_t ThreadSafeIdMap<V>::Insert(std::unique_ptr<V> value) {
222 absl::MutexLock lock(&mutex_);
223 const int64_t result = next_id_;
224 ++next_id_;
225 pending_inserts_.push_back(std::make_pair(result, std::move(value)));
226 has_pending_modifications_ = true;
227 return result;
228}
229
230template <typename V>
231bool ThreadSafeIdMap<V>::Erase(const int64_t key) {
232 absl::MutexLock lock(&mutex_);
233 for (auto it = pending_inserts_.begin(); it != pending_inserts_.end(); ++it) {
234 if (it->first == key) {
235 pending_inserts_.erase(it);
236 UpdateHasPendingModifications();
237 return true;
238 }
239 }
240 for (const auto& [k, v] : elements_) {
241 if (k == key) {
242 auto [unused, inserted] = pending_deletes_.insert(k);
243 if (inserted) {
244 UpdateHasPendingModifications();
245 }
246 return inserted;
247 }
248 }
249 return false;
250}
251
252template <typename V>
254 if (!pending_deletes_.empty()) {
256 &elements_, [this](const std::pair<int64_t, std::unique_ptr<V>>& entry)
257 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
258 return pending_deletes_.contains(entry.first);
259 });
260 pending_deletes_.clear();
261 }
262 for (auto& kv : pending_inserts_) {
263 elements_.push_back(std::move(kv));
264 }
265 pending_inserts_.clear();
266 has_pending_modifications_ = false;
267}
268
269template <typename V>
271 has_pending_modifications_ =
272 !pending_inserts_.empty() || !pending_deletes_.empty();
273}
274
275} // namespace operations_research::math_opt
276
277#endif // OR_TOOLS_MATH_OPT_ELEMENTAL_THREAD_SAFE_ID_MAP_H_
absl::Span< const std::pair< int64_t, std::unique_ptr< V > > > UpdateAndGetAll()
int64_t Insert(std::unique_ptr< V > value)
Inserts value into the map and returns the assigned key.
bool Erase(int64_t key)
Erases key from the map, returning true if the key was found in the map.
std::vector< std::pair< int64_t, V * > > GetAll() const
int64_t Size() const
The number of elements in the map.
void STLEraseAllFromSequenceIf(T *v, P pred)
Remove each element e in v satisfying pred(e).
Definition stl_util.h:104
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
problem is infeasible or unbounded (default).
Definition matchers.h:468
STL namespace.