ALib C++ Library
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
condition.inl
Go to the documentation of this file.
1//==================================================================================================
2/// \file
3/// This header-file is part of module \alib_threads of the \aliblong.
4///
5/// \emoji :copyright: 2013-2025 A-Worx GmbH, Germany.
6/// Published under \ref mainpage_license "Boost Software License".
7//==================================================================================================
8#if !ALIB_SINGLE_THREADED
9ALIB_EXPORT namespace alib { namespace threads {
10
11
12/// This struct wraps C++ standard library types \c std::mutex and \c std::condition_variable.
13/// It allows one or more threads to sleep until any other thread satisfies a condition and
14/// wakes one or all threads up.
15///
16/// \par Abstract Template Type:
17/// This class is an <em>"abstract template type"</em>, which means that it invokes methods
18/// which are not declared and thus have to be declared by a type that derives from this class.
19/// The derived type needs to pass its own type name to template parameter \p{TDerived}.<br>
20/// With that, this type can cast its <c>this</c>-pointer to that type and access its interface.
21/// The derived type has to provide only one function member:
22///
23/// bool isConditionMet()
24/// \par
25/// This has to return \c true if the condition is fulfilled, otherwise \c false.
26///
27/// \par Acquisition Rules:
28/// Before using one of the overloaded blocking sleep methods #WaitForNotification, the object
29/// has to be locked by calling #Acquire.
30/// Such acquirement is guaranteed to be held after the method returns.
31/// \par
32/// A notifying thread has to likewise acquire this object before calling either
33/// #ReleaseAndNotify or #ReleaseAndNotifyAll.
34/// However, as the method names indicate, with the return from the notification call, the
35/// internal mutex is released.
36/// \par
37/// Similar to class \alib{threads;Lock}, nested calls to the method #Acquire are not allowed and
38/// constitute undefined behavior.
39///
40/// \par Usage Recipe:
41/// Being an <em>"abstract"</em> type, the class is not designed for direct use.
42/// Instead, a derived type should expose its own, dedicated interface, which replaces
43/// this type's interface.
44/// As a sample, let us look at provided derived type \alib{threads;Condition}, which implements
45/// the simplest possible use-case of this class:
46/// - The type has protected inheritance to this struct.
47/// - Protected method \alib{threads::Condition;isConditionMet} is defined, which returns \c true if
48/// the instance was notified.
49/// - This parent struct is made a friend too enable access to the protected method.
50/// - Own interface methods are provided. In this case methods
51/// \alib{threads::Condition;Notify}, \alib{threads::Condition;NotifyAll}, and
52/// \alib{threads::Condition;Wait}.
53/// \par
54/// A more sophisticated but still simple implementation can be reviewed by analyzing the
55/// source of type job-queues in classes \alib{threadmodel;DedicatedWorker} and
56/// \alib{threadmodel;ThreadPool}.
57/// Its corresponding interface methods are <em>push</em> and <em>pop</em> alike,
58/// which do not resemble too much to the terms wait/notify anymore.
59///
60/// \par Debug-Information:
61/// With debug-builds, several assertions are made to avoid wrong use of the type.
62/// This is implemented by adding the field member #Dbg.
63/// The output format of assertions should be 'clickable' inside a users' IDE.
64/// The default output string is optimized for
65/// \https{JetBrains CLion,www.jetbrains.com/clion} and can be changed by manipulating
66/// the static field member \alib{threads;DbgConditionAsserter::ASSERTION_FORMAT}.
67///
68/// \par Availability
69/// This type is not available if the compiler-symbol \ref ALIB_SINGLE_THREADED is set.
70///
71/// @see
72/// - Chapter \ref alib_manual_appendix_callerinfo of the General Programmer's Manual.
73/// - For this class, a \ref alibtools_debug_helpers_gdb "pretty printer" for the
74/// GNU debugger is provided.
75///
76/// @tparam TDerived The derived type which provides method \b isConditionMet.
77template<typename TDerived>
79{
80 /// The mutex used for locking this instance.
81 std::mutex mutex;
82
83 /// The condition variable used for blocking and notification.
84 std::condition_variable conditionVariable;
85
86 /// Helper to statically casts this object to the derived type.
87 /// @return <c>*this</c> cast to a reference of \p{TDerived}.
88 TDerived& cast() { return static_cast<TDerived&>(*this); }
89
90
91 #if ALIB_DEBUG
92 /// The debug tool instance.
94
95 /// Constructor.
96 /// @param dbgName The name of this instance.<br>
97 /// Available only with debug-builds.
98 /// With release-builds, this type is default
99 /// constructed. Hence, this parameter has to be provided using macro
100 /// \ref ALIB_DBG, which prunes the name away.
101 TCondition(const character* dbgName) { Dbg.Name= dbgName; }
102 #else
103 TCondition() = default;
104 #endif
105
106 /// A thread which invokes this method gets registered as the current owner of this object,
107 /// until the same thread releases the ownership invoking #Release.
108 /// In the case that this object is already owned by another thread, the invoking thread is
109 /// suspended until ownership can be gained.<p>
110 /// Multiple (nested) calls to this method are <b>not supported</b> and constitute
111 /// undefined behavior. In debug-compilations, an \ref alib_mod_assert "error is raised".
112 ///
113 /// An instance has to be acquired before invoking any of the notifiy- or wait-methods.
114 /// When return from a notification method, the instance is released.
115 /// With return from a wait method, the instance is still held.
116 ///
117 /// \par Debug Parameter:
118 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
120 #if ALIB_DEBUG
121 Dbg.Assert( Dbg.Owner != Thread::GetCurrent(), ALIB_CALLER, ci,
122 "Acquire: Multiple acquirements of TCondition are forbidden." );
123 #endif
124
125 mutex.lock();
126
127 #if ALIB_DEBUG
128 Dbg.Assert( Dbg.Owner==nullptr, ALIB_CALLER, ci,
129 "Acquire: Owner is (still) set, after std::mutex.lock()." );
130 Dbg.AcqCI = ci;
131 Dbg.Owner = Thread::Get(ci.ThreadID);
132 #endif
133 }
134
135 /// Releases ownership of this object.
136 /// If this method is invoked on an object that is not acquired or acquired by a different
137 /// thread, in debug-compilations an \ref alib_mod_assert "error is raised".
138 /// In release compilations, this leads to undefined behavior.
139 /// @see Method #Acquire.
140 /// \par Debug Parameter:
141 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
143 #if ALIB_DEBUG
144 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci, "Release: Not acquired." );
145 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
146 "Release: Ownership is with a different thread" );
147 Dbg.RelCI = ci;
148 Dbg.Owner= nullptr;
149 #endif
150
151 mutex.unlock();
152 }
153
154 /// Unblock a next waiting thread.<p>
155 /// As the method name indicates, with this implementation, it is necessary to acquire this
156 /// object before invoking this method.
157 /// Internally, the mutex will be released, and thus no separate call to #Release is necessary,
158 /// respectively allowed.
159 ///
160 /// \par Debug Parameter:
161 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
163 #if ALIB_DEBUG
164 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
165 "ReleaseAndNotify called without prior acquisition" );
166
167 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
168 "ReleaseAndNotify: Ownership is with a different thread" );
169
170 Dbg.Owner= nullptr;
171 Dbg.NotifyCI= ci;
172 Dbg.Owner= nullptr;
173 #endif
174
175 mutex.unlock();
176 conditionVariable.notify_one();
177 }
178
179 /// Releases the internal mutex and wakes up all sleeping threads.
180 /// \par Debug Parameter:
181 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
183 #if ALIB_DEBUG
184 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter), ALIB_CALLER, ci,
185 "An exclusive waiter is set. Thus, notifying 'all' is not allowed.");
186
187 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
188 "ReleaseAndNotify called without prior acquisition" );
189
190 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
191 "ReleaseAndNotify: Ownership is with a different thread" );
192
193 Dbg.Owner= nullptr;
194 Dbg.NotifyCI= ci;
195 #endif
196
197 mutex.unlock();
198 conditionVariable.notify_all();
199 }
200
201 /// Waits for notification (for an unlimited time).<br>
202 /// Before invoking this method, this object has to be \ref Acquire "acquired".
203 /// After the wake-up call, the internal mutex is (again) acquired and thus has to be
204 /// released by the owner.<br>
205 /// It is allowed to create (endless) loops that never actively release this instance
206 /// but call one of the waiting methods instead.<br>
207 /// Note that "spurious wake-ups" are internally caught with this implementation.
208 /// \par Debug Parameter:
209 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
211 #if ALIB_DEBUG
212 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter)
213 || Dbg.AssertExclusiveWaiter == std::this_thread::get_id(), ALIB_CALLER, ci,
214 "WaitForNotification called by a different thread than granted with 'Dbg.AssertExclusiveWaiter'.");
215
216 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
217 "WaitForNotification called without prior acquisition" );
218
219 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
220 "WaitForNotification: Ownership is with a different thread" );
221
222 ++Dbg.CntWaiters;
223 Dbg.WaitCI= ci;
224 Dbg.Owner= nullptr;
225 #endif
226
227 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
228 conditionVariable.wait( lock, [this]{ return cast().isConditionMet(); } );
229 lock.release();
230
231 #if ALIB_DEBUG
232 Dbg.Owner= Thread::Get(ci.ThreadID);
233 --Dbg.CntWaiters;
234 #endif
235 }
236
237 #if !ALIB_DEBUG
238 void WaitForNotification( const Ticks::Duration::TDuration& maxWaitTimeSpan)
239 {
240 #else
241 /// Same as #WaitForNotification(ALIB_DBG_TAKE_CI), but takes a C++ time span that defines
242 /// a maximum wait time.
243 ///
244 /// @param maxWaitTimeSpan The maximum time to wait.
245 /// @param ci Caller information.
246 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
247 void WaitForNotification( const Ticks::Duration::TDuration& maxWaitTimeSpan,
248 const CallerInfo& ci ) {
249 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter)
250 || Dbg.AssertExclusiveWaiter == std::this_thread::get_id(), ALIB_CALLER, ci,
251 "WaitForNotification called by a different thread than granted with 'Dbg.AssertExclusiveWaiter'.");
252
253 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
254 "WaitForNotification called without prior acquisition" );
255
256 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
257 "WaitForNotification: Ownership is with a different thread" );
258
259 ++Dbg.CntWaiters;
260 Dbg.WaitCI= ci;
261 Dbg.Owner= nullptr;
262 #endif
263
264 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
265 conditionVariable.wait_for( lock, maxWaitTimeSpan, [this]{ return cast().isConditionMet(); } );
266 lock.release();
267
268 #if ALIB_DEBUG
269 Dbg.Owner= Thread::Get(ci.ThreadID);
270 --Dbg.CntWaiters;
271 #endif
272 }
273
274 #if ALIB_DEBUG
275 /// Same as #WaitForNotification(ALIB_DBG_TAKE_CI), but takes a time span that defines
276 /// a maximum wait time.
277 ///
278 /// @param maxWaitTimeSpan The maximum time to wait.
279 /// @param ci Caller information.
280 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
281 void WaitForNotification( const Ticks::Duration& maxWaitTimeSpan, const CallerInfo& ci )
282 { WaitForNotification( maxWaitTimeSpan.Export(), ci ); }
283 #else
284 void WaitForNotification( const Ticks::Duration& maxWaitTimeSpan )
285 { WaitForNotification( maxWaitTimeSpan.Export() ); }
286 #endif
287
288
289 #if !ALIB_DEBUG
290 void WaitForNotification( const Ticks& wakeUpTime )
291 {
292 #else
293 /// Same as #WaitForNotification(ALIB_DBG_TAKE_CI), but takes a point in time at which
294 /// waiting stops.
295 ///
296 /// @param wakeUpTime The point in time to wake up, even if not notified.
297 /// @param ci Caller information.
298 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
299 void WaitForNotification( const Ticks& wakeUpTime, const CallerInfo& ci ) {
300 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter)
301 || Dbg.AssertExclusiveWaiter == std::this_thread::get_id(), ALIB_CALLER, ci,
302 "WaitForNotification called by a different thread than granted with 'Dbg.AssertExclusiveWaiter'.");
303
304 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
305 "WaitForNotification called without prior acquisition" );
306
307 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
308 "WaitForNotification: Ownership is with a different thread" );
309
310
311 ++Dbg.CntWaiters;
312 Dbg.WaitCI= ci;
313 Dbg.Owner= nullptr;
314 #endif
315
316 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
317 conditionVariable.wait_until( lock, wakeUpTime.Export(),
318 [this]{ return cast().isConditionMet(); } );
319 lock.release();
320
321 #if ALIB_DEBUG
322 Dbg.Owner= Thread::Get(ci.ThreadID);
323 --Dbg.CntWaiters;
324 #endif
325 }
326};
327
328/// Extends <em>"abstract template type"</em> \alib{threads;TCondition}.
329/// This implementation constitutes the simplest possible derivate, by
330/// 1. holding just a boolean member, and
331/// 2. by providing a similar generic interface.
332/// @see Chapter \ref alib_manual_appendix_callerinfo of the General Programmer's Manual.
333class Condition : protected TCondition<Condition>
334{
335 /// the parent type needs to be able to call protected method #isConditionMet.
336 friend struct TCondition<Condition>;
337
338 protected:
339 /// Boolean member which records notifications. Defaults to not-notified.
340 bool notified = false;
341
342 /// In general, derivates of \alib{threads;TCondition} have to return \c true if the former
343 /// reason for blocking a thread is now fulfilled.
344 ///
345 /// @return This implementation returns the value of field member #notified.
346 bool isConditionMet() { return notified; }
347
348 public:
349 using TCondition<Condition>::Acquire;
350 using TCondition<Condition>::Release;
351
352 //################################################################################################
353 //### Debug implementation
354 //################################################################################################
355 #if ALIB_DEBUG
356 /// Defaulted default constructor.
357 /// @param dbgName A name for the condition. Only available with debug-compilations.
358 Condition(const character* dbgName) : TCondition(dbgName) {}
359 #else
360 Condition() = default;
361 #endif
362
363 /// Wakes up the next sleeping thread.
364 /// \par Debug Parameter:
365 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
367 Acquire(ALIB_DBG(ci));
368 notified= true;
370 }
371
372 /// Wakes up all sleeping threads.
373 /// \par Debug Parameter:
374 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
376 Acquire(ALIB_DBG(ci));
377 notified= true;
379 }
380
381 /// Waits for notification (for an unlimited time).<p>
382 /// \par Debug Parameter:
383 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
385 #if ALIB_DEBUG
386 Acquire(ci);
387 notified= false;
389 Release(ci);
390 #else
392 notified= false;
394 #endif
395 }
396
397 #if ALIB_DEBUG
398 /// Same as #Wait(const Ticks::Duration&, const CallerInfo&), but takes a
399 /// C++ time span.
400 ///
401 /// @param maxWaitTimeSpan The maximum time to wait.
402 /// @param ci Caller information.
403 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
404 void Wait( const Ticks::Duration::TDuration& maxWaitTimeSpan, const CallerInfo& ci ) {
405 Acquire(ci);
406 notified= false;
407 WaitForNotification(maxWaitTimeSpan, ci);
408 Release(ci);
409 }
410 #else
411 void Wait( const Ticks::Duration::TDuration& maxWaitTimeSpan )
412 {ALIB_LOCK
413 notified= false;
414 WaitForNotification(maxWaitTimeSpan);
415 }
416 #endif
417
418
419 #if ALIB_DEBUG
420 /// Waits for notification but only for a given duration.
421 ///
422 /// Before invoking this method, this object has to be \ref Acquire "acquired".
423 /// After the wake-up call, the internal mutex is (again) acquired and thus has to be
424 /// released later.
425 ///
426 /// @param maxWaitTimeSpan The maximum time to wait.
427 /// @param ci Caller information.
428 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
429 void Wait( const Ticks::Duration& maxWaitTimeSpan, const CallerInfo& ci )
430 { Wait( maxWaitTimeSpan.Export(), ci ); }
431 #else
432 void Wait( const Ticks::Duration& maxWaitTimeSpan )
433 { Wait( maxWaitTimeSpan.Export() ); }
434 #endif
435
436 #if ALIB_DEBUG
437 /// Waits for notification, but only until a given point in time.
438 ///
439 /// Before invoking this method, this object has to be \ref Acquire "acquired".
440 /// After the wake-up call, the internal mutex is (again) acquired and thus has to be
441 /// released later.
442 ///
443 /// @param wakeUpTime The point in time to wake up, even if not notified.
444 /// @param ci Caller information.
445 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
446 void Wait( const Ticks& wakeUpTime, const CallerInfo& ci ) {
447 Acquire(ci);
448 notified= false;
449 WaitForNotification(wakeUpTime, ci);
450 Release(ci);
451 }
452 #else
453 void Wait( const Ticks& wakeUpTime )
454 {ALIB_LOCK
455 notified= false;
456 WaitForNotification(wakeUpTime);
457 }
458 #endif
459};
460
461} // namespace alib[threads]
462
463/// Type alias in namespace \b alib.
464template<typename T= bool>
466
467/// Type alias in namespace \b alib.
469
470} // namespace [alib]
471
472
473#endif // !ALIB_SINGLE_THREADED
Condition(const character *dbgName)
void Notify(ALIB_DBG_TAKE_CI)
bool notified
Boolean member which records notifications. Defaults to not-notified.
void NotifyAll(ALIB_DBG_TAKE_CI)
void Wait(const Ticks::Duration::TDuration &maxWaitTimeSpan, const CallerInfo &ci)
void Wait(const Ticks &wakeUpTime, const CallerInfo &ci)
void Wait(const Ticks::Duration &maxWaitTimeSpan, const CallerInfo &ci)
void Wait(ALIB_DBG_TAKE_CI)
static Thread * GetCurrent()
Definition thread.inl:298
static ALIB_DLL Thread * Get(std::thread::id nativeID)
Definition thread.cpp:314
TTimePoint Export() const
#define ALIB_DBG_TAKE_CI
Definition alib.inl:1030
#define ALIB_CALLER
Definition alib.inl:1018
#define ALIB_EXPORT
Definition alib.inl:497
#define ALIB_LOCK
Definition alib.inl:1336
#define ALIB_DBG(...)
Definition alib.inl:853
constexpr bool IsNotNull(const T &t)
Definition tmp.inl:55
constexpr bool IsNull(const T &t)
Definition tmp.inl:48
threads::TCondition< T > TCondition
Type alias in namespace alib.
lang::CallerInfo CallerInfo
Type alias in namespace alib.
time::Ticks Ticks
Type alias in namespace alib.
Definition ticks.inl:79
threads::Condition Condition
Type alias in namespace alib.
characters::character character
Type alias in namespace alib.
std::thread::id ThreadID
The ID of the calling thread.
DbgConditionAsserter Dbg
Definition condition.inl:93
void WaitForNotification(ALIB_DBG_TAKE_CI)
void ReleaseAndNotifyAll(ALIB_DBG_TAKE_CI)
TCondition(const character *dbgName)
std::condition_variable conditionVariable
Definition condition.inl:84
void Release(ALIB_DBG_TAKE_CI)
void Acquire(ALIB_DBG_TAKE_CI)
void WaitForNotification(const Ticks::Duration &maxWaitTimeSpan, const CallerInfo &ci)
void WaitForNotification(const Ticks::Duration::TDuration &maxWaitTimeSpan, const CallerInfo &ci)
void ReleaseAndNotify(ALIB_DBG_TAKE_CI)