Repository navigation
Expand file tree
/
Copy pathUniqueLocker.mpp
More file actions
285 lines (258 loc) · 9.64 KB
/
Copy pathUniqueLocker.mpp
File metadata and controls
285 lines (258 loc) · 9.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
export module CppUtils.Thread.UniqueLocker;
import std;
export namespace CppUtils::Thread
{
/**
* @brief RAII accessor providing exclusive read and write access to a protected value.
*
* Does not own the value, only holds a reference and manages the underlying lock guard.
*
* @see https://morgancaron.github.io/CppUtils/fr/guides/lockers/ (Guide de synchronisation RAII)
* @see https://morgancaron.github.io/CppUtils/en/guides/lockers/ (RAII synchronization guide)
*
* @tparam T The type of the value being accessed.
* @tparam Mutex The mutex type used for synchronization (defaults to std::mutex).
*/
template<class T, class Mutex = std::mutex>
class Accessor final
{
public:
/**
* @brief Constructs an accessor by acquiring a unique lock on the provided mutex.
* @param mutex Mutex to lock.
* @param value Reference to the value to protect.
*/
inline explicit constexpr Accessor(Mutex& mutex, T& value):
m_value{std::ref(value)}
{
if not consteval
{
m_lockGuard.emplace(mutex);
}
}
/// @brief Returns a reference to the protected value.
[[nodiscard]] inline constexpr auto value(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> decltype(auto)
{
return self.m_value.get();
}
/// @brief Accesses member functions or fields of the protected value.
[[nodiscard]] inline constexpr auto operator->(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> auto*
{
return std::addressof(self.value());
}
/// @brief Dereferences to obtain the protected value (equivalent to calling value()).
[[nodiscard]] inline constexpr auto operator*(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> decltype(auto)
{
return self.value();
}
/**
* @brief Returns a reference to the underlying @c std::unique_lock guard.
*
* Provides direct access to the lock guard, allowing it to be used with
* synchronization primitives requiring a @c std::unique_lock (such as @c std::condition_variable::wait).
*
* When used with @c std::condition_variable::wait, the wait function atomically releases the mutex while
* placing the thread to sleep, and re-acquires the lock upon waking before returning once the predicate evaluates to true:
* @code
* auto accessor = queue.access();
* condition.wait(accessor.getLockGuard(), [&] {
* return not std::ranges::empty(accessor.value());
* });
* auto item = accessor->pop();
* @endcode
*
* @return Reference to the internal @c std::unique_lock<Mutex>.
*/
[[nodiscard]] inline constexpr auto getLockGuard(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> auto&
{
return self.m_lockGuard.value();
}
private:
mutable std::optional<std::unique_lock<Mutex>> m_lockGuard;
std::reference_wrapper<T> m_value;
};
/**
* @brief RAII accessor providing exclusive read-only access to a const-qualified value.
*
* Holds an exclusive lock (std::unique_lock) preventing concurrent access by other threads,
* but restricts operations on the protected value to const member functions and read-only inspection.
*
* @see https://morgancaron.github.io/CppUtils/fr/guides/lockers/ (Guide de synchronisation RAII)
* @see https://morgancaron.github.io/CppUtils/en/guides/lockers/ (RAII synchronization guide)
*
* @tparam T The type of the value being accessed (const-qualified).
* @tparam Mutex The mutex type used for synchronization.
*/
template<class T, class Mutex>
class Accessor<const T, Mutex> final
{
public:
/**
* @brief Constructs a const accessor by acquiring a unique lock on the provided mutex.
* @param mutex Mutex to lock.
* @param value Const reference to the value to protect.
*/
inline explicit constexpr Accessor(Mutex& mutex, const T& value):
m_value{std::cref(value)}
{
if not consteval
{
m_lockGuard.emplace(mutex);
}
}
/// @brief Returns a const reference to the protected value.
[[nodiscard]] inline constexpr auto value(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> decltype(auto)
{
return self.m_value.get();
}
/// @brief Accesses const member functions or fields of the protected value.
[[nodiscard]] inline constexpr auto operator->(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> auto*
{
return std::addressof(self.value());
}
/// @brief Dereferences to obtain the protected const value (equivalent to calling value()).
[[nodiscard]] inline constexpr auto operator*(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> decltype(auto)
{
return self.value();
}
/**
* @brief Returns a reference to the underlying @c std::unique_lock guard.
*
* Provides direct access to the lock guard, allowing it to be used with
* synchronization primitives requiring a @c std::unique_lock (such as @c std::condition_variable::wait).
*
* When used with @c std::condition_variable::wait, the wait function atomically releases the mutex while
* placing the thread to sleep, and re-acquires the lock upon waking before returning once the predicate evaluates to true:
* @code
* auto accessor = state.access();
* condition.wait(accessor.getLockGuard(), [&] {
* return accessor->isReady();
* });
* @endcode
*
* @return Reference to the internal @c std::unique_lock<Mutex>.
*/
[[nodiscard]] inline constexpr auto getLockGuard(this auto&& self [[clang::lifetimebound, msvc::lifetimebound]]) noexcept -> auto&
{
return self.m_lockGuard.value();
}
private:
mutable std::optional<std::unique_lock<Mutex>> m_lockGuard;
std::reference_wrapper<const T> m_value;
};
template<class Mutex, class T>
Accessor(Mutex&, T&) -> Accessor<T, Mutex>;
/**
* @brief Monitor Object wrapper encapsulating a resource with a mutex.
*
* Ensures that all access to the underlying resource must pass through an Accessor RAII object
* that holds the mutex lock, guaranteeing at compile time that access to the protected data is synchronized.
*
* @see https://morgancaron.github.io/CppUtils/fr/guides/lockers/ (Guide de synchronisation RAII)
* @see https://morgancaron.github.io/CppUtils/en/guides/lockers/ (RAII synchronization guide)
*
* @tparam T The type of the protected resource.
*/
template<class T>
class UniqueLocker final
{
public:
/**
* @brief Constructs the protected resource in place.
* @param args Arguments forwarded to the resource constructor.
*/
inline explicit UniqueLocker(auto&&... args):
m_value{std::forward<decltype(args)>(args)...}
{}
UniqueLocker(const UniqueLocker&) = delete;
auto operator=(const UniqueLocker&) -> UniqueLocker& = delete;
/// @brief Move constructor. Acquires lock on the source locker before moving its value.
inline UniqueLocker(UniqueLocker&& other) noexcept(std::is_nothrow_move_constructible_v<T>):
m_value{std::move(other.access().value())}
{}
/// @brief Move assignment. Acquires scoped lock on both mutexes before moving the value.
inline auto operator=(UniqueLocker&& other) noexcept(std::is_nothrow_move_assignable_v<T>) -> UniqueLocker&
{
if (this != std::addressof(other))
{
auto guard = std::scoped_lock{m_mutex, other.m_mutex};
m_value = std::move(other.m_value);
}
return *this;
}
/**
* @brief Acquires exclusive read/write access to the resource.
* @return Accessor object managing the lock and providing access.
*/
[[nodiscard]] inline auto access() & -> Accessor<T, std::mutex>
{
return Accessor{m_mutex, m_value};
}
/**
* @brief Acquires exclusive read-only access to the resource from an lvalue reference.
* @return Const Accessor object managing the lock.
*/
inline auto access() const& -> Accessor<const T, std::mutex>
{
return Accessor{m_mutex, m_value};
}
/**
* @brief Acquires exclusive read-only access to the resource from an rvalue reference.
* @return Const Accessor object managing the lock.
*/
inline auto access() const&& -> Accessor<const T, std::mutex>
{
return Accessor{m_mutex, m_value};
}
private:
mutable std::mutex m_mutex;
T m_value;
template<class... Lockers>
friend struct MultipleAccessor;
};
template<class T>
UniqueLocker(T) -> UniqueLocker<T>;
/**
* @brief RAII structure locking multiple lockers simultaneously without deadlocks.
*
* Uses std::scoped_lock internally to acquire all locks atomically in a deadlock-free manner,
* regardless of the order lockers are passed across different threads.
*
* @see https://morgancaron.github.io/CppUtils/fr/guides/lockers/ (Guide de synchronisation RAII)
* @see https://morgancaron.github.io/CppUtils/en/guides/lockers/ (RAII synchronization guide)
*
* @code
* auto account0 = CppUtils::Thread::UniqueLocker<Account>{};
* auto account1 = CppUtils::Thread::UniqueLocker<Account>{};
*
* {
* auto lockBoth = CppUtils::Thread::MultipleAccessor{account0, account1};
* auto& [firstAccount, secondAccount] = lockBoth.values;
* firstAccount.withdraw(100);
* secondAccount.deposit(100);
* }
* @endcode
*
* @tparam Lockers Variadic list of locker types to lock simultaneously.
*/
template<class... Lockers>
struct MultipleAccessor final
{
/**
* @brief Locks all provided lockers atomically.
* @param lockers Variadic references to lockers to lock.
*/
inline constexpr MultipleAccessor(Lockers&... lockers):
values{std::tie(lockers.m_value...)}
{
if not consteval
{
m_lockGuard.emplace(lockers.m_mutex...);
}
}
private:
std::optional<std::scoped_lock<decltype(std::declval<Lockers>().m_mutex)...>> m_lockGuard;
public:
std::tuple<decltype(std::declval<Lockers>().m_value)&...> values;
};
}