Geant4 11.1.1
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
AutoLock.hh
Go to the documentation of this file.
1//
2// MIT License
3// Copyright (c) 2020 Jonathan R. Madsen
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED
12// "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
13// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
15// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
16// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
17// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18//
19//
20// ---------------------------------------------------------------
21// Tasking class header file
22//
23/// Class Description:
24///
25/// This class provides a mechanism to create a mutex and locks/unlocks it.
26/// Can be used by applications to implement in a portable way a mutexing logic.
27/// Usage Example:
28///
29/// #include "Threading.hh"
30/// #include "AutoLock.hh"
31///
32/// /// defined somewhere -- static so all threads see the same mutex
33/// static Mutex aMutex;
34///
35/// /// somewhere else:
36/// /// The AutoLock instance will automatically unlock the mutex when it
37/// /// goes out of scope. One typically defines the scope within { } if
38/// /// there is thread-safe code following the auto-lock
39///
40/// {
41/// AutoLock l(&aMutex);
42/// ProtectedCode();
43/// }
44///
45/// UnprotectedCode();
46///
47/// /// When ProtectedCode() is calling a function that also tries to lock
48/// /// a normal AutoLock + Mutex will "deadlock". In other words, the
49/// /// the mutex in the ProtectedCode() function will wait forever to
50/// /// acquire the lock that is being held by the function that called
51/// /// ProtectedCode(). In this situation, use a RecursiveAutoLock +
52/// /// RecursiveMutex, e.g.
53///
54/// /// defined somewhere -- static so all threads see the same mutex
55/// static RecursiveMutex aRecursiveMutex;
56///
57/// /// this function is sometimes called directly and sometimes called
58/// /// from SomeFunction_B(), which also locks the mutex
59/// void SomeFunction_A()
60/// {
61/// /// when called from SomeFunction_B(), a Mutex + AutoLock will
62/// /// deadlock
63/// RecursiveAutoLock l(&aRecursiveMutex);
64/// /// do something
65/// }
66///
67/// void SomeFunction_B()
68/// {
69///
70/// {
71/// RecursiveAutoLock l(&aRecursiveMutex);
72/// SomeFunction_A();
73/// }
74///
75/// UnprotectedCode();
76/// }
77///
78///
79/// ---------------------------------------------------------------
80/// Author: Andrea Dotti (15 Feb 2013): First Implementation
81///
82/// Update: Jonathan Madsen (9 Feb 2018): Replaced custom implementation
83/// with inheritance from C++11 unique_lock, which inherits the
84/// following member functions:
85///
86/// - unique_lock(unique_lock&& other) noexcept;
87/// - explicit unique_lock(mutex_type& m);
88/// - unique_lock(mutex_type& m, std::defer_lock_t t) noexcept;
89/// - unique_lock(mutex_type& m, std::try_to_lock_t t);
90/// - unique_lock(mutex_type& m, std::adopt_lock_t t);
91///
92/// - template <typename Rep, typename Period>
93/// unique_lock(mutex_type& m,
94/// const std::chrono::duration<Rep,Period>&
95/// timeout_duration);
96///
97/// - template<typename Clock, typename Duration>
98/// unique_lock(mutex_type& m,
99/// const std::chrono::time_point<Clock,Duration>& timeout_time);
100///
101/// - void lock();
102/// - void unlock();
103/// - bool try_lock();
104///
105/// - template <typename Rep, typename Period>
106/// bool try_lock_for(const std::chrono::duration<Rep,Period>&);
107///
108/// - template <typename Rep, typename Period>
109/// bool try_lock_until(const std::chrono::time_point<Clock,Duration>&);
110///
111/// - void swap(unique_lock& other) noexcept;
112/// - mutex_type* release() noexcept;
113/// - mutex_type* mutex() const noexcept;
114/// - bool owns_lock() const noexcept;
115/// - explicit operator bool() const noexcept;
116/// - unique_lock& operator=(unique_lock&& other);
117///
118/// ---------------------------------------------------------------
119///
120/// Note that AutoLock is defined also for a sequential Tasking build but below
121/// regarding implementation (also found in Threading.hh)
122///
123///
124/// NOTE ON Tasking SERIAL BUILDS AND MUTEX/UNIQUE_LOCK
125/// ==================================================
126///
127/// Mutex and RecursiveMutex are always C++11 std::mutex types
128/// however, in serial mode, using MUTEXLOCK and MUTEXUNLOCK on these
129/// types has no effect -- i.e. the mutexes are not actually locked or unlocked
130///
131/// Additionally, when a Mutex or RecursiveMutex is used with AutoLock
132/// and RecursiveAutoLock, respectively, these classes also suppressing
133/// the locking and unlocking of the mutex. Regardless of the build type,
134/// AutoLock and RecursiveAutoLock inherit from std::unique_lock<std::mutex>
135/// and std::unique_lock<std::recursive_mutex>, respectively. This means
136/// that in situations (such as is needed by the analysis category), the
137/// AutoLock and RecursiveAutoLock can be passed to functions requesting
138/// a std::unique_lock. Within these functions, since std::unique_lock
139/// member functions are not virtual, they will not retain the dummy locking
140/// and unlocking behavior
141/// --> An example of this behavior can be found below
142///
143/// Jonathan R. Madsen (February 21, 2018)
144///
145/***
146
147//======================================================================================//
148
149typedef std::unique_lock<std::mutex> unique_lock_t;
150// functions for casting AutoLock to std::unique_lock to demonstrate
151// that AutoLock is NOT polymorphic
152void as_unique_lock(unique_lock_t* lock) { lock->lock(); }
153void as_unique_unlock(unique_lock_t* lock) { lock->unlock(); }
154
155//======================================================================================//
156
157void run(const uint64_t& n)
158{
159 // sync the threads a bit
160 std::this_thread::sleep_for(std::chrono::milliseconds(10));
161
162 // get two mutexes to avoid deadlock when l32 actually locks
163 AutoLock l32(TypeMutex<int32_t>(), std::defer_lock);
164 AutoLock l64(TypeMutex<int64_t>(), std::defer_lock);
165
166 // when serial: will not execute std::unique_lock::lock() because
167 // it overrides the member function
168 l32.lock();
169 // regardless of serial or MT: will execute std::unique_lock::lock()
170 // because std::unique_lock::lock() is not virtual
171 as_unique_lock(&l64);
172
173 std::cout << "Running iteration " << n << "..." << std::endl;
174}
175
176//======================================================================================//
177// execute some work
178template <typename thread_type = std::thread>
179void exec(uint64_t n)
180{
181 // get two mutexes to avoid deadlock when l32 actually locks
182 AutoLock l32(TypeMutex<int32_t>(), std::defer_lock);
183 AutoLock l64(TypeMutex<int64_t>(), std::defer_lock);
184
185 std::vector<thread_type*> threads(n, nullptr);
186 for(uint64_t i = 0; i < n; ++i)
187 {
188 threads[i] = new thread_type();
189 *(threads[i]) = std::move(thread_type(run, i));
190 }
191
192 // when serial: will not execute std::unique_lock::lock() because
193 // it overrides the member function
194 l32.lock();
195 // regardless of serial or MT: will execute std::unique_lock::lock()
196 // because std::unique_lock::lock() is not virtual
197 as_unique_lock(&l64);
198
199 std::cout << "Joining..." << std::endl;
200
201 // when serial: will not execute std::unique_lock::unlock() because
202 // it overrides the member function
203 l32.unlock();
204 // regardless of serial or MT: will execute std::unique_lock::unlock()
205 // because std::unique_lock::unlock() is not virtual
206 as_unique_unlock(&l64);
207
208 // NOTE ABOUT UNLOCKS:
209 // in MT, commenting out either
210 // l32.unlock();
211 // or
212 // as_unique_unlock(&l64);
213 // creates a deadlock; in serial, commenting out
214 // as_unique_unlock(&l64);
215 // creates a deadlock but commenting out
216 // l32.unlock();
217 // does not
218
219 // clean up and join
220 for(uint64_t i = 0; i < n; ++i)
221 {
222 threads[i]->join();
223 delete threads[i];
224 }
225 threads.clear();
226}
227
228//======================================================================================//
229
230int main()
231{
232 print_threading();
233
234 uint64_t n = 30;
235 std::cout << "\nRunning with real threads...\n" << std::endl;
236 exec<std::thread>(n);
237 std::cout << "\nRunning with fake threads...\n" << std::endl;
238 exec<DummyThread>(n);
239
240}
241
242***/
243
244#pragma once
245
246#include "PTL/Threading.hh"
247#include "PTL/Utility.hh"
248
249#include <chrono>
250#include <iostream>
251#include <mutex>
252#include <system_error>
253
254namespace PTL
255{
256// Note: Note that TemplateAutoLock by itself is not thread-safe and
257// cannot be shared among threads due to the locked switch
258//
259template <typename MutexT>
260class TemplateAutoLock : public std::unique_lock<MutexT>
261{
262public:
263 //------------------------------------------------------------------------//
264 // Some useful typedefs
265 //------------------------------------------------------------------------//
266 using unique_lock_t = std::unique_lock<MutexT>;
268 using mutex_type = typename unique_lock_t::mutex_type;
269
270public:
271 //------------------------------------------------------------------------//
272 // STL-consistent reference form constructors
273 //------------------------------------------------------------------------//
274
275 // reference form is consistent with STL lock_guard types
276 // Locks the associated mutex by calling m.lock(). The behavior is
277 // undefined if the current thread already owns the mutex except when
278 // the mutex is recursive
279 explicit TemplateAutoLock(mutex_type& _mutex)
280 : unique_lock_t(_mutex, std::defer_lock)
281 {
282 // call termination-safe locking. if serial, this call has no effect
283 _lock_deferred();
284 }
285
286 // Tries to lock the associated mutex by calling
287 // m.try_lock_for(_timeout_duration). Blocks until specified
288 // _timeout_duration has elapsed or the lock is acquired, whichever comes
289 // first. May block for longer than _timeout_duration.
290 template <typename Rep, typename Period>
292 const std::chrono::duration<Rep, Period>& _timeout_duration)
293 : unique_lock_t(_mutex, std::defer_lock)
294 {
295 // call termination-safe locking. if serial, this call has no effect
296 _lock_deferred(_timeout_duration);
297 }
298
299 // Tries to lock the associated mutex by calling
300 // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
301 // been reached or the lock is acquired, whichever comes first. May block
302 // for longer than until _timeout_time has been reached.
303 template <typename Clock, typename Duration>
305 const std::chrono::time_point<Clock, Duration>& _timeout_time)
306 : unique_lock_t(_mutex, std::defer_lock)
307 {
308 // call termination-safe locking. if serial, this call has no effect
309 _lock_deferred(_timeout_time);
310 }
311
312 // Does not lock the associated mutex.
313 TemplateAutoLock(mutex_type& _mutex, std::defer_lock_t _lock) noexcept
314 : unique_lock_t(_mutex, _lock)
315 {}
316
317 // Tries to lock the associated mutex without blocking by calling
318 // m.try_lock(). The behavior is undefined if the current thread already
319 // owns the mutex except when the mutex is recursive.
320 TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t _lock)
321 : unique_lock_t(_mutex, _lock)
322 {}
323
324 // Assumes the calling thread already owns m
325 TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t _lock)
326 : unique_lock_t(_mutex, _lock)
327 {}
328
329public:
330 //------------------------------------------------------------------------//
331 // Backwards compatibility versions (constructor with pointer to mutex)
332 //------------------------------------------------------------------------//
334 : unique_lock_t(*_mutex, std::defer_lock)
335 {
336 // call termination-safe locking. if serial, this call has no effect
337 _lock_deferred();
338 }
339
340 TemplateAutoLock(mutex_type* _mutex, std::defer_lock_t _lock) noexcept
341 : unique_lock_t(*_mutex, _lock)
342 {}
343
344 TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t _lock)
345 : unique_lock_t(*_mutex, _lock)
346 {}
347
348 TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t _lock)
349 : unique_lock_t(*_mutex, _lock)
350 {}
351
352private:
353// helpful macros
354#define _is_stand_mutex(Tp) (std::is_same<Tp, Mutex>::value)
355#define _is_recur_mutex(Tp) (std::is_same<Tp, RecursiveMutex>::value)
356#define _is_other_mutex(Tp) (!_is_stand_mutex(Tp) && !_is_recur_mutex(Tp))
357
358 template <typename Tp = MutexT,
359 typename std::enable_if<_is_stand_mutex(Tp), int>::type = 0>
360 std::string GetTypeString()
361 {
362 return "AutoLock<Mutex>";
363 }
364
365 template <typename Tp = MutexT,
366 typename std::enable_if<_is_recur_mutex(Tp), int>::type = 0>
367 std::string GetTypeString()
368 {
369 return "AutoLock<RecursiveMutex>";
370 }
371
372 template <typename Tp = MutexT,
373 typename std::enable_if<_is_other_mutex(Tp), int>::type = 0>
374 std::string GetTypeString()
375 {
376 return "AutoLock<UNKNOWN_MUTEX>";
377 }
378
379// pollution is bad
380#undef _is_stand_mutex
381#undef _is_recur_mutex
382#undef _is_other_mutex
383
384 //========================================================================//
385 // NOTE on _lock_deferred(...) variants:
386 // a system_error in lock means that the mutex is unavailable
387 // we want to throw the error that comes from locking an unavailable
388 // mutex so that we know there is a memory leak
389 // if the mutex is valid, this will hold until the other thread
390 // finishes
391
392 // sometimes certain destructors use locks, this isn't an issue unless
393 // the object is leaked. When this occurs, the application finalization
394 // (i.e. the real or implied "return 0" part of main) will call destructors
395 // on Tasking object after some static mutex variables are deleted, leading
396 // to the error code (typically on Clang compilers):
397 // libc++abi.dylib: terminating with uncaught exception of type
398 // std::__1::system_error: mutex lock failed: Invalid argument
399 // this function protects against this failure until such a time that
400 // these issues have been resolved
401
402 //========================================================================//
403 // standard locking
404 inline void _lock_deferred()
405 {
406 try
407 {
408 this->unique_lock_t::lock();
409 } catch(std::system_error& e)
410 {
411 PrintLockErrorMessage(e);
412 }
413 }
414
415 //========================================================================//
416 // Tries to lock the associated mutex by calling
417 // m.try_lock_for(_timeout_duration). Blocks until specified
418 // _timeout_duration has elapsed or the lock is acquired, whichever comes
419 // first. May block for longer than _timeout_duration.
420 template <typename Rep, typename Period>
421 void _lock_deferred(const std::chrono::duration<Rep, Period>& _timeout_duration)
422 {
423 try
424 {
425 this->unique_lock_t::try_lock_for(_timeout_duration);
426 } catch(std::system_error& e)
427 {
428 PrintLockErrorMessage(e);
429 }
430 }
431
432 //========================================================================//
433 // Tries to lock the associated mutex by calling
434 // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
435 // been reached or the lock is acquired, whichever comes first. May block
436 // for longer than until _timeout_time has been reached.
437 template <typename Clock, typename Duration>
438 void _lock_deferred(const std::chrono::time_point<Clock, Duration>& _timeout_time)
439 {
440 try
441 {
442 this->unique_lock_t::try_lock_until(_timeout_time);
443 } catch(std::system_error& e)
444 {
445 PrintLockErrorMessage(e);
446 }
447 }
448
449 //========================================================================//
450 // the message for what mutex lock fails due to deleted static mutex
451 // at termination
452 void PrintLockErrorMessage(std::system_error& e)
453 {
454 // use std::cout/std::endl to avoid include dependencies
455 using std::cout;
456 using std::endl;
457 // the error that comes from locking an unavailable mutex
458#if defined(VERBOSE)
459 cout << "Non-critical error: mutex lock failure in "
460 << GetTypeString<mutex_type>() << ". "
461 << "If the app is terminating, Tasking failed to "
462 << "delete an allocated resource and a Tasking destructor is "
463 << "being called after the statics were destroyed. \n\t--> "
464 << "Exception: [code: " << e.code() << "] caught: " << e.what() << std::endl;
465#else
467#endif
468 }
469};
470
471// -------------------------------------------------------------------------- //
472//
473// Use the non-template types below:
474// - AutoLock with Mutex
475// - RecursiveAutoLock with RecursiveMutex
476//
477// -------------------------------------------------------------------------- //
478
481
482} // namespace PTL
#define _is_other_mutex(Tp)
Definition: AutoLock.hh:356
#define _is_stand_mutex(Tp)
Definition: AutoLock.hh:354
#define _is_recur_mutex(Tp)
Definition: AutoLock.hh:355
TemplateAutoLock(mutex_type *_mutex, std::try_to_lock_t _lock)
Definition: AutoLock.hh:344
TemplateAutoLock(mutex_type &_mutex, std::defer_lock_t _lock) noexcept
Definition: AutoLock.hh:313
TemplateAutoLock(mutex_type *_mutex)
Definition: AutoLock.hh:333
TemplateAutoLock(mutex_type &_mutex, std::try_to_lock_t _lock)
Definition: AutoLock.hh:320
TemplateAutoLock(mutex_type &_mutex, const std::chrono::duration< Rep, Period > &_timeout_duration)
Definition: AutoLock.hh:291
TemplateAutoLock(mutex_type *_mutex, std::defer_lock_t _lock) noexcept
Definition: AutoLock.hh:340
TemplateAutoLock(mutex_type *_mutex, std::adopt_lock_t _lock)
Definition: AutoLock.hh:348
TemplateAutoLock(mutex_type &_mutex, std::adopt_lock_t _lock)
Definition: AutoLock.hh:325
TemplateAutoLock(mutex_type &_mutex)
Definition: AutoLock.hh:279
typename unique_lock_t::mutex_type mutex_type
Definition: AutoLock.hh:268
std::unique_lock< MutexT > unique_lock_t
Definition: AutoLock.hh:266
TemplateAutoLock(mutex_type &_mutex, const std::chrono::time_point< Clock, Duration > &_timeout_time)
Definition: AutoLock.hh:304
Definition: AutoLock.hh:255
void ConsumeParameters(Args &&...)
Definition: Utility.hh:44