ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
sleeper.hpp
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-2024 A-Worx GmbH, Germany.
6 * Published under \ref mainpage_license "Boost Software License".
7 **************************************************************************************************/
8#ifndef HPP_ALIB_THREADS_SLEEPER
9#define HPP_ALIB_THREADS_SLEEPER 1
10
11#if !defined (HPP_ALIB_LANG_OWNER)
12 #include "alib/lang/owner.hpp"
13#endif
14
15#if !defined(HPP_ALIB_THREADS_THREAD)
17#endif
18
19#if !defined (HPP_ALIB_STRINGS_LOCALSTRING)
21#endif
22
23#if !defined (_GLIBCXX_MUTEX) && !defined(_MUTEX_)
24 #include <mutex>
25#endif
26
27#if !defined (_GLIBCXX_CONDITION_VARIABLE) && !defined(_CONDITION_VARIABLE_)
28 #include <condition_variable>
29#endif
30
31namespace alib { namespace threads {
32
33/** ************************************************************************************************
34 * This class is a simple wrapper around C++ standard library types \c std::mutex and
35 * \c std::condition_variable and allows a thread to sleep until another thread wakes
36 * the thread up.
37 *
38 * Prior to using one of the overloaded sleep methods #WaitForNotification, the object
39 * has to be \ref Acquire "acquired". During sleep, a notifying thread may, but in most
40 * situations should not acquire this object. Further details are given with the documentation
41 * of method #Notify.
42 *
43 * This class does not allow repeated calls to method #Acquire without prior invocations of
44 * #Release. Repeated acquisitions cause undefined behavior.
45 * With debug builds, an assertion is raised when #Acquire is invoked while the lock is already
46 * acquired. In this respect, this class compares to that of \alib{threads;ThreadLockNR} and not
47 * to that of \alib{threads;ThreadLock}
48 *
49 * Multiple threads may sleep in parallel using a single instance of this object.
50 * The notification thread may either wake up one of them or wake all up by using
51 * #NotifyAll.
52 **************************************************************************************************/
54{
55 protected:
56 /** The mutex used for locking. */
57 typename std::mutex mutex;
58
59 /** The condition variable used for sleeping and notification to wake up. */
60 std::condition_variable event;
61
62 /** Flag used to detect "spurious" wake-ups. */
64
65 #if ALIB_DEBUG
66 /** Source location of acquirement. (Available only in debug-builds.). */
68
69 /** Source location of acquirement. (Available only in debug-builds.). */
71
72 /** Source location of acquirement. (Available only in debug-builds.). */
74
75
76 /** Id of a thread that currently acquired this object's mutex, used to detect
77 * invocations of methods #WaitForNotification and #Release without prior acquirement,
78 * as well as forbidden multiple (nested) acquirements.s
79 * Available only in debug compilations. */
80 std::thread::id dbgIsAcquiredBy;
81 #endif
82
83 public:
84 /** ****************************************************************************************
85 * Default constructor.
86 ******************************************************************************************/
87 Sleeper() = default;
88
89 /** ****************************************************************************************
90 * A thread which invokes this method gets registered as the current owner of this object,
91 * until the same thread releases the ownership invoking #Release.
92 * In the case that this object is already owned by another thread, the invoking thread is
93 * suspended until ownership can be gained.<p>
94 * Multiple (nested) calls to this method are <b>not supported</b> and lead to
95 * undefined behavior. In debug-compilations, an assertion is raised.
96 *
97 * \note
98 * In debug-compilations of the library, this method accepts three parameters,
99 * providing information about the caller. In the release version these parameters do not
100 * exist. Therefore use macro \ref ALIB_CALLER_PRUNED to provide the parameters:
101 *
102 * sample.Acquire( ALIB_CALLER_PRUNED );
103 *
104 * @param dbgFile Caller information. Available only with debug builds.
105 * @param dbgLine Caller information. Available only with debug builds.
106 * @param dbgFunc Caller information. Available only with debug builds.
107 ******************************************************************************************/
108 #if ALIB_DEBUG
109 void Acquire( const NCString& dbgFile, int dbgLine, const NCString& dbgFunc )
110 {
111 DbgOwnerFile= dbgFile;
112 DbgOwnerLine= dbgLine;
113 DbgOwnerFunc= dbgFunc;
114 #else
115 void Acquire()
116 {
117 #endif
118 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::this_thread::get_id(),
119 "THREADS", "Multiple acquirements of Sleeper are forbidden." )
120 mutex.lock();
121 ALIB_DBG( dbgIsAcquiredBy= std::this_thread::get_id(); )
122 }
123
124 /** ****************************************************************************************
125 * Releases ownership of this object.
126 * If this method is invoked on an object that is not acquired or acquired by a different
127 * thread, in debug-compilations an assertion is raised.
128 * In release compilations, this leads to undefined behavior.
129 ******************************************************************************************/
130 void Release()
131 {
132 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::thread::id() ,
133 "THREADS", "Release without prior acquisition" )
134 ALIB_ASSERT_ERROR( dbgIsAcquiredBy == std::this_thread::get_id(), "THREADS",
135 NString256() <<
136 "Release while ownership is with a different thread.\n" <<
137 " This thread: " << Thread::GetCurrent()->GetName() << " (ID: "
138 << Thread::GetCurrent()->GetId() << ")\n"
139 " Owner: " << detail::getThread(dbgIsAcquiredBy)->GetName() << " (ID: "
140 << detail::getThread(dbgIsAcquiredBy)->GetId() << ")"
141 )
142 ALIB_DBG( dbgIsAcquiredBy= std::thread::id(); )
143 mutex.unlock();
144 }
145
146
147 /** ****************************************************************************************
148 * Wakes up the next sleeping thread.<p>
149 * It is not necessary and not even recommended to acquiring this object, prior to waking
150 * the next thread up. However, in some situations, it should be done. Further
151 * explanation on this is given with the documentation of the standard C++ library,
152 * which is quoted here:<p>
153 * <em>
154 * "The notifying thread does not need to hold the lock on the same mutex as the one held
155 * by the waiting thread(s); in fact doing so is a pessimization, since the notified thread
156 * would immediately block again, waiting for the notifying thread to release the lock.
157 * However, some implementations (in particular many implementations of pthreads) recognize
158 * this situation and avoid this "hurry up and wait" scenario by transferring the waiting
159 * thread from the condition variable's queue directly to the queue of the mutex within
160 * the notify call, without waking it up.<p>
161 * Notifying while under the lock may nevertheless be necessary when precise scheduling of
162 * events is required, e.g. if the waiting thread would exit the program if the condition
163 * is satisfied, causing destruction of the notifying thread's condition variable.
164 * A spurious wakeup after mutex unlock but before notify would result in notify called
165 * on a destroyed object.
166 * </em>
167 ******************************************************************************************/
168 void Notify()
169 {
170 wasNotified= true;
171 event.notify_one();
172 }
173
174 /** ****************************************************************************************
175 * Wakes up all sleeping threads.<p>
176 * \see For explanations about the necessity of acquiring this object prior to
177 * to notification, see documentation of method #Notify.
178 ******************************************************************************************/
180 {
181 wasNotified= true;
182 event.notify_all();
183 }
184
185 /** ****************************************************************************************
186 * Waits for notification (for an unlimited time).<p>
187 * Prior to invoking this method, this object has to be \ref Acquire "acquired".
188 * After the wake up call, the internal mutex is (again) acquired and thus has to be
189 * released later.
190 ******************************************************************************************/
192 {
193 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::thread::id() ,
194 "THREADS", "Wait method called without prior acquisition" )
195 ALIB_ASSERT_ERROR( dbgIsAcquiredBy == std::this_thread::get_id(), "THREADS",
196 NString256() <<
197 "Wait method called while owned by another thread.\n" <<
198 " This thread: " << Thread::GetCurrent()->GetName() << " (ID: "
199 << Thread::GetCurrent()->GetId() << ")\n"
200 " Owner: " << detail::getThread(dbgIsAcquiredBy)->GetName() << " (ID: "
201 << detail::getThread(dbgIsAcquiredBy)->GetId() << ")"
202 )
203
204 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
205 wasNotified= false;
206 event.wait( lock, [this]{ return wasNotified; } );
207 }
208
209#if ALIB_TIME
210 /** ****************************************************************************************
211 * Waits for notification, but only fore a given duration..<p>
212 * Prior to invoking this method, this object has to be \ref Acquire "acquired".
213 * After the wake up call, the internal mutex is (again) acquired and thus has to be
214 * released later.
215 *
216 * \note In the absense of module \alib_time, this method has template parameters and
217 * parameter \p maxSleepTime will be defined like in
218 * <c>std::condition_variable::wait_for</c> as
219 * <c>const std::chrono::duration<Rep, Period>&</c>.
220 *
221 * @param maxSleepTime The maximum time to wait.
222 ******************************************************************************************/
223 void WaitForNotification( const Ticks::Duration& maxSleepTime )
224 {
225 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::thread::id() ,
226 "THREADS", "Wait method called without prior acquisition" )
227 ALIB_ASSERT_ERROR( dbgIsAcquiredBy == std::this_thread::get_id(), "THREADS",
228 NString256() <<
229 "Wait method called while owned by another thread.\n" <<
230 " This thread: " << Thread::GetCurrent()->GetName() << " (ID: "
231 << Thread::GetCurrent()->GetId() << ")\n"
232 " Owner: " << detail::getThread(dbgIsAcquiredBy)->GetName() << " (ID: "
233 << detail::getThread(dbgIsAcquiredBy)->GetId() << ")" )
234 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
235 wasNotified= false;
236 event.wait_for( lock, maxSleepTime.Export(), [this]{ return wasNotified; } );
237 }
238
239 /** ****************************************************************************************
240 * Waits for notification, but only fore a given duration.
241 * Prior to invoking this method, this object has to be \ref Acquire "acquired".
242 * After the wake up call, the internal mutex is (again) acquired and thus has to be
243 * released later.
244 *
245 * \note In the absense of module \alib_time, this method has template parameters and
246 * parameter \p maxSleepTime will be defined like in
247 * <c>std::condition_variable::WaitForNotification</c> as
248 * <c>const std::chrono::time_point<Clock, Duration>&</c>.
249 *
250 * @param wakeUpTime The point in time to wake up, even if not notified.
251 ******************************************************************************************/
252 void WaitForNotification( const Ticks& wakeUpTime )
253 {
254 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::thread::id() ,
255 "THREADS", "Wait method called without prior acquisition" )
256 ALIB_ASSERT_ERROR( dbgIsAcquiredBy == std::this_thread::get_id(), "THREADS",
257 NString256() <<
258 "Wait method called while owned by another thread.\n" <<
259 " This thread: " << Thread::GetCurrent()->GetName() << " (ID: "
260 << Thread::GetCurrent()->GetId() << ")\n"
261 " Owner: " << detail::getThread(dbgIsAcquiredBy)->GetName() << " (ID: "
262 << detail::getThread(dbgIsAcquiredBy)->GetId() << ")"
263 )
264 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
265 wasNotified= false;
266 event.wait_until( lock, wakeUpTime.Export(), [this]{ return wasNotified; } );
267 }
268#else
269 template< class Rep, class Period >
270 void WaitForNotification( const std::chrono::duration<Rep, Period>& maxSleepTime )
271 {
272 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::thread::id() ,
273 "THREADS", "Wait method called without prior acquisition" )
274 ALIB_ASSERT_ERROR( dbgIsAcquiredBy == std::this_thread::get_id(), "THREADS",
275 NString256() <<
276 "Wait method called while owned by another thread.\n" <<
277 " This thread: " << Thread::GetCurrent()->GetName() << " (ID: "
278 << Thread::GetCurrent()->GetId() << ")\n"
279 " Owner: " << detail::getThread(dbgIsAcquiredBy)->GetName() << " (ID: "
280 << detail::getThread(dbgIsAcquiredBy)->GetId() << ")" )
281 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
282 wasNotified= false;
283 event.wait_for( lock, maxSleepTime, [this]{ return wasNotified; } );
284 }
285
286 template< class Clock, class Duration >
287 void WaitForNotification( const std::chrono::time_point<Clock, Duration>& wakeUpTime )
288 {
289 ALIB_ASSERT_ERROR( dbgIsAcquiredBy != std::thread::id() ,
290 "THREADS", "Wait method called without prior acquisition" )
291 ALIB_ASSERT_ERROR( dbgIsAcquiredBy == std::this_thread::get_id(), "THREADS",
292 NString256() <<
293 "Wait method called while owned by another thread.\n" <<
294 " This thread: " << Thread::GetCurrent()->GetName() << " (ID: "
295 << Thread::GetCurrent()->GetId() << ")\n"
296 " Owner: " << detail::getThread(dbgIsAcquiredBy)->GetName() << " (ID: "
297 << detail::getThread(dbgIsAcquiredBy)->GetId() << ")"
298 )
299 std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
300 wasNotified= false;
301 event.wait_until( lock, wakeUpTime , [this]{ return wasNotified; } );
302 }
303#endif
304
305};
306
307
308} // namespace alib[::threads]
309
310/// Type alias in namespace \b alib.
312
313} // namespace [alib]
314
315#endif // HPP_ALIB_THREADS_SLEEPER
std::condition_variable event
Definition sleeper.hpp:60
void WaitForNotification(const Ticks &wakeUpTime)
Definition sleeper.hpp:252
void WaitForNotification(const Ticks::Duration &maxSleepTime)
Definition sleeper.hpp:223
void Acquire(const NCString &dbgFile, int dbgLine, const NCString &dbgFunc)
Definition sleeper.hpp:109
std::thread::id dbgIsAcquiredBy
Definition sleeper.hpp:80
static Thread * GetCurrent()
Definition thread.hpp:288
TTimePoint Export() const
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:984
#define ALIB_DBG(...)
Definition alib.hpp:457
ALIB_API Thread * getThread(std::thread::id c11ID)
Definition thread.cpp:96
Definition alib.cpp:57
lang::Owner< TOwnable > Owner
Type alias in namespace alib.
Definition owner.hpp:101
threads::Thread Thread
Type alias in namespace alib.
Definition thread.hpp:390
NLocalString< 256 > NString256
Type alias name for TLocalString<nchar,256> .