ALib C++ Library
Library Version: 2510 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 //==============================================================================================
107 /// A thread which invokes this method gets registered as the current owner of this object,
108 /// until the same thread releases the ownership invoking #Release.
109 /// In the case that this object is already owned by another thread, the invoking thread is
110 /// suspended until ownership can be gained.<p>
111 /// Multiple (nested) calls to this method are <b>not supported</b> and constitute
112 /// undefined behavior. In debug-compilations, an \ref alib_mod_assert "error is raised".
113 ///
114 /// An instance has to be acquired before invoking any of the notifiy- or wait-methods.
115 /// When return from a notification method, the instance is released.
116 /// With return from a wait method, the instance is still held.
117 ///
118 /// \par Debug Parameter:
119 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
120 //==============================================================================================
122 {
123 #if ALIB_DEBUG
124 Dbg.Assert( Dbg.Owner != Thread::GetCurrent(), ALIB_CALLER, ci,
125 "Acquire: Multiple acquirements of TCondition are forbidden." );
126 #endif
127
128 mutex.lock();
129
130 #if ALIB_DEBUG
131 Dbg.Assert( Dbg.Owner==nullptr, ALIB_CALLER, ci,
132 "Acquire: Owner is (still) set, after std::mutex.lock()." );
133 Dbg.AcqCI = ci;
134 Dbg.Owner = Thread::Get(ci.ThreadID);
135 #endif
136 }
137
138 //==============================================================================================
139 /// Releases ownership of this object.
140 /// If this method is invoked on an object that is not acquired or acquired by a different
141 /// thread, in debug-compilations an \ref alib_mod_assert "error is raised".
142 /// In release compilations, this leads to undefined behavior.
143 /// @see Method #Acquire.
144 /// \par Debug Parameter:
145 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
146 //==============================================================================================
148 {
149 #if ALIB_DEBUG
150 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci, "Release: Not acquired." );
151 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
152 "Release: Ownership is with a different thread" );
153 Dbg.RelCI = ci;
154 Dbg.Owner= nullptr;
155 #endif
156
157 mutex.unlock();
158 }
159
160 //==============================================================================================
161 /// Unblock a next waiting thread.<p>
162 /// As the method name indicates, with this implementation, it is necessary to acquire this
163 /// object before invoking this method.
164 /// Internally, the mutex will be released, and thus no separate call to #Release is necessary,
165 /// respectively allowed.
166 ///
167 /// \par Debug Parameter:
168 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
169 //==============================================================================================
171 {
172 #if ALIB_DEBUG
173 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
174 "ReleaseAndNotify called without prior acquisition" );
175
176 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
177 "ReleaseAndNotify: Ownership is with a different thread" );
178
179 Dbg.Owner= nullptr;
180 Dbg.NotifyCI= ci;
181 Dbg.Owner= nullptr;
182 #endif
183
184 mutex.unlock();
185 conditionVariable.notify_one();
186 }
187
188 //==============================================================================================
189 /// Releases the internal mutex and wakes up all sleeping threads.
190 /// \par Debug Parameter:
191 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
192 //==============================================================================================
194 {
195 #if ALIB_DEBUG
196 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter), ALIB_CALLER, ci,
197 "An exclusive waiter is set. Thus, notifying 'all' is not allowed.");
198
199 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
200 "ReleaseAndNotify called without prior acquisition" );
201
202 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
203 "ReleaseAndNotify: Ownership is with a different thread" );
204
205 Dbg.Owner= nullptr;
206 Dbg.NotifyCI= ci;
207 #endif
208
209 mutex.unlock();
210 conditionVariable.notify_all();
211 }
212
213 //==============================================================================================
214 /// Waits for notification (for an unlimited time).<br>
215 /// Before invoking this method, this object has to be \ref Acquire "acquired".
216 /// After the wake-up call, the internal mutex is (again) acquired and thus has to be
217 /// released by the owner.<br>
218 /// It is allowed to create (endless) loops that never actively release this instance
219 /// but call one of the waiting methods instead.<br>
220 /// Note that "spurious wake-ups" are internally caught with this implementation.
221 /// \par Debug Parameter:
222 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
223 //==============================================================================================
225 {
226 #if ALIB_DEBUG
227 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter)
228 || Dbg.AssertExclusiveWaiter == std::this_thread::get_id(), ALIB_CALLER, ci,
229 "WaitForNotification called by a different thread than granted with 'Dbg.AssertExclusiveWaiter'.");
230
231 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
232 "WaitForNotification called without prior acquisition" );
233
234 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
235 "WaitForNotification: Ownership is with a different thread" );
236
237 ++Dbg.CntWaiters;
238 Dbg.WaitCI= ci;
239 Dbg.Owner= nullptr;
240 #endif
241
242 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
243 conditionVariable.wait( lock, [this]{ return cast().isConditionMet(); } );
244 lock.release();
245
246 #if ALIB_DEBUG
247 Dbg.Owner= Thread::Get(ci.ThreadID);
248 --Dbg.CntWaiters;
249 #endif
250 }
251
252 #if !ALIB_DEBUG
253 void WaitForNotification( const Ticks::Duration::TDuration& maxWaitTimeSpan)
254 {
255 #else
256 //==============================================================================================
257 /// Same as #WaitForNotification(ALIB_DBG_TAKE_CI), but takes a C++ time span that defines
258 /// a maximum wait time.
259 ///
260 /// @param maxWaitTimeSpan The maximum time to wait.
261 /// @param ci Caller information.
262 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
263 //==============================================================================================
264 void WaitForNotification( const Ticks::Duration::TDuration& maxWaitTimeSpan, const CallerInfo& ci )
265 {
266 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter)
267 || Dbg.AssertExclusiveWaiter == std::this_thread::get_id(), ALIB_CALLER, ci,
268 "WaitForNotification called by a different thread than granted with 'Dbg.AssertExclusiveWaiter'.");
269
270 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
271 "WaitForNotification called without prior acquisition" );
272
273 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
274 "WaitForNotification: Ownership is with a different thread" );
275
276 ++Dbg.CntWaiters;
277 Dbg.WaitCI= ci;
278 Dbg.Owner= nullptr;
279 #endif
280
281 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
282 conditionVariable.wait_for( lock, maxWaitTimeSpan, [this]{ return cast().isConditionMet(); } );
283 lock.release();
284
285 #if ALIB_DEBUG
286 Dbg.Owner= Thread::Get(ci.ThreadID);
287 --Dbg.CntWaiters;
288 #endif
289 }
290
291 #if ALIB_DEBUG
292 //==============================================================================================
293 /// Same as #WaitForNotification(ALIB_DBG_TAKE_CI), but takes a time span that defines
294 /// a maximum wait time.
295 ///
296 /// @param maxWaitTimeSpan The maximum time to wait.
297 /// @param ci Caller information.
298 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
299 //==============================================================================================
300 void WaitForNotification( const Ticks::Duration& maxWaitTimeSpan, const CallerInfo& ci )
301 { WaitForNotification( maxWaitTimeSpan.Export(), ci ); }
302 #else
303 void WaitForNotification( const Ticks::Duration& maxWaitTimeSpan )
304 { WaitForNotification( maxWaitTimeSpan.Export() ); }
305 #endif
306
307
308 #if !ALIB_DEBUG
309 void WaitForNotification( const Ticks& wakeUpTime )
310 {
311 #else
312 //==============================================================================================
313 /// Same as #WaitForNotification(ALIB_DBG_TAKE_CI), but takes a point in time at which
314 /// waiting stops.
315 ///
316 /// @param wakeUpTime The point in time to wake up, even if not notified.
317 /// @param ci Caller information.
318 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
319 //==============================================================================================
320 void WaitForNotification( const Ticks& wakeUpTime, const CallerInfo& ci )
321 {
322 Dbg.Assert( lang::IsNull(Dbg.AssertExclusiveWaiter)
323 || Dbg.AssertExclusiveWaiter == std::this_thread::get_id(), ALIB_CALLER, ci,
324 "WaitForNotification called by a different thread than granted with 'Dbg.AssertExclusiveWaiter'.");
325
326 Dbg.Assert( lang::IsNotNull(Dbg.Owner), ALIB_CALLER, ci,
327 "WaitForNotification called without prior acquisition" );
328
329 Dbg.Assert( Dbg.Owner == Thread::GetCurrent(), ALIB_CALLER, ci,
330 "WaitForNotification: Ownership is with a different thread" );
331
332
333 ++Dbg.CntWaiters;
334 Dbg.WaitCI= ci;
335 Dbg.Owner= nullptr;
336 #endif
337
338 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
339 conditionVariable.wait_until( lock, wakeUpTime.Export(),
340 [this]{ return cast().isConditionMet(); } );
341 lock.release();
342
343 #if ALIB_DEBUG
344 Dbg.Owner= Thread::Get(ci.ThreadID);
345 --Dbg.CntWaiters;
346 #endif
347 }
348};
349
350/// Extends <em>"abstract template type"</em> \alib{threads;TCondition}.
351/// This implementation constitutes the simplest possible derivate, by
352/// 1. holding just a boolean member, and
353/// 2. by providing a similar generic interface.
354/// @see
355/// Chapter \ref alib_manual_appendix_callerinfo of the General Programmer's Manual.
356class Condition : protected TCondition<Condition>
357{
358 /// the parent type needs to be able to call protected method #isConditionMet.
359 friend struct TCondition<Condition>;
360
361 protected:
362 /// Boolean member which records notifications. Defaults to not-notified.
363 bool notified = false;
364
365 /// In general, derivates of \alib{threads;TCondition} have to return \c true if the former
366 /// reason for blocking a thread is now fulfilled.
367 ///
368 /// @return This implementation returns the value of field member #notified.
369 bool isConditionMet() { return notified; }
370
371 public:
372 using TCondition<Condition>::Acquire;
373 using TCondition<Condition>::Release;
374
375 //################################################################################################
376 //### Debug implementation
377 //################################################################################################
378 #if ALIB_DEBUG
379 /// Defaulted default constructor.
380 /// @param dbgName A name for the condition. Only available with debug-compilations.
381 Condition(const character* dbgName) : TCondition(dbgName) {}
382 #else
383 Condition() = default;
384 #endif
385
386 /// Wakes up the next sleeping thread.
387 /// \par Debug Parameter:
388 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
390 {
391 Acquire(ALIB_DBG(ci));
392 notified= true;
394 }
395
396 /// Wakes up all sleeping threads.
397 /// \par Debug Parameter:
398 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
400 {
401 Acquire(ALIB_DBG(ci));
402 notified= true;
404 }
405
406 /// Waits for notification (for an unlimited time).<p>
407 /// \par Debug Parameter:
408 /// Pass macro \ref ALIB_CALLER_PRUNED with invocations.
410 {
411 #if ALIB_DEBUG
412 Acquire(ci);
413 notified= false;
415 Release(ci);
416 #else
418 notified= false;
420 #endif
421 }
422
423 #if ALIB_DEBUG
424 //==============================================================================================
425 /// Same as #Wait(const Ticks::Duration&, const CallerInfo&), but takes a
426 /// C++ time span.
427 ///
428 /// @param maxWaitTimeSpan The maximum time to wait.
429 /// @param ci Caller information.
430 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
431 //==============================================================================================
432 void Wait( const Ticks::Duration::TDuration& maxWaitTimeSpan, const CallerInfo& ci )
433 {
434 Acquire(ci);
435 notified= false;
436 WaitForNotification(maxWaitTimeSpan, ci);
437 Release(ci);
438 }
439 #else
440 void Wait( const Ticks::Duration::TDuration& maxWaitTimeSpan )
441 {ALIB_LOCK
442 notified= false;
443 WaitForNotification(maxWaitTimeSpan);
444 }
445 #endif
446
447
448 #if ALIB_DEBUG
449 //==============================================================================================
450 /// Waits for notification but only for a given duration.
451 ///
452 /// Before invoking this method, this object has to be \ref Acquire "acquired".
453 /// After the wake-up call, the internal mutex is (again) acquired and thus has to be
454 /// released later.
455 ///
456 /// @param maxWaitTimeSpan The maximum time to wait.
457 /// @param ci Caller information.
458 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
459 //==============================================================================================
460 void Wait( const Ticks::Duration& maxWaitTimeSpan, const CallerInfo& ci )
461 { Wait( maxWaitTimeSpan.Export(), ci ); }
462 #else
463 void Wait( const Ticks::Duration& maxWaitTimeSpan )
464 { Wait( maxWaitTimeSpan.Export() ); }
465 #endif
466
467 #if ALIB_DEBUG
468 //==============================================================================================
469 /// Waits for notification, but only until a given point in time.
470 ///
471 /// Before invoking this method, this object has to be \ref Acquire "acquired".
472 /// After the wake-up call, the internal mutex is (again) acquired and thus has to be
473 /// released later.
474 ///
475 /// @param wakeUpTime The point in time to wake up, even if not notified.
476 /// @param ci Caller information.
477 /// Use macro \ref ALIB_COMMA_CALLER_PRUNED with invocations.
478 //==============================================================================================
479 void Wait( const Ticks& wakeUpTime, const CallerInfo& ci )
480 {
481 Acquire(ci);
482 notified= false;
483 WaitForNotification(wakeUpTime, ci);
484 Release(ci);
485 }
486 #else
487 void Wait( const Ticks& wakeUpTime )
488 {ALIB_LOCK
489 notified= false;
490 WaitForNotification(wakeUpTime);
491 }
492 #endif
493};
494
495} // namespace alib[threads]
496
497/// Type alias in namespace \b alib.
498template<typename T= bool>
500
501/// Type alias in namespace \b alib.
503
504} // namespace [alib]
505
506
507#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:300
static ALIB_DLL Thread * Get(std::thread::id nativeID)
Definition thread.cpp:329
TTimePoint Export() const
#define ALIB_DBG_TAKE_CI
Definition alib.inl:1013
#define ALIB_CALLER
Definition alib.inl:1001
#define ALIB_EXPORT
Definition alib.inl:488
#define ALIB_LOCK
Definition alib.inl:1319
#define ALIB_DBG(...)
Definition alib.inl:836
constexpr bool IsNotNull(const T &t)
Definition tmp.inl:56
constexpr bool IsNull(const T &t)
Definition tmp.inl:49
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:109
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)