Geant4 10.7.0
Toolkit for the simulation of the passage of particles through matter
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
248#include <chrono>
249#include <iostream>
250#include <mutex>
251#include <system_error>
252
253namespace PTL
254{
255// Note: Note that TemplateAutoLock by itself is not thread-safe and
256// cannot be shared among threads due to the locked switch
257//
258template <typename MutexT>
259class TemplateAutoLock : public std::unique_lock<MutexT>
260{
261public:
262 //------------------------------------------------------------------------//
263 // Some useful typedefs
264 //------------------------------------------------------------------------//
265 typedef std::unique_lock<MutexT> unique_lock_t;
267 typedef typename unique_lock_t::mutex_type mutex_type;
268
269public:
270 //------------------------------------------------------------------------//
271 // STL-consistent reference form constructors
272 //------------------------------------------------------------------------//
273
274 // reference form is consistent with STL lock_guard types
275 // Locks the associated mutex by calling m.lock(). The behavior is
276 // undefined if the current thread already owns the mutex except when
277 // the mutex is recursive
278 explicit TemplateAutoLock(mutex_type& _mutex)
279 : unique_lock_t(_mutex, std::defer_lock)
280 {
281 // call termination-safe locking. if serial, this call has no effect
282 _lock_deferred();
283 }
284
285 // Tries to lock the associated mutex by calling
286 // m.try_lock_for(_timeout_duration). Blocks until specified
287 // _timeout_duration has elapsed or the lock is acquired, whichever comes
288 // first. May block for longer than _timeout_duration.
289 template <typename Rep, typename Period>
291 const std::chrono::duration<Rep, Period>& _timeout_duration)
292 : unique_lock_t(_mutex, std::defer_lock)
293 {
294 // call termination-safe locking. if serial, this call has no effect
295 _lock_deferred(_timeout_duration);
296 }
297
298 // Tries to lock the associated mutex by calling
299 // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
300 // been reached or the lock is acquired, whichever comes first. May block
301 // for longer than until _timeout_time has been reached.
302 template <typename Clock, typename Duration>
304 const std::chrono::time_point<Clock, Duration>& _timeout_time)
305 : unique_lock_t(_mutex, std::defer_lock)
306 {
307 // call termination-safe locking. if serial, this call has no effect
308 _lock_deferred(_timeout_time);
309 }
310
311 // Does not lock the associated mutex.
312 TemplateAutoLock(mutex_type& _mutex, std::defer_lock_t _lock) noexcept
313 : unique_lock_t(_mutex, _lock)
314 {}
315
316 // Tries to lock the associated mutex without blocking by calling
317 // m.try_lock(). The behavior is undefined if the current thread already
318 // owns the mutex except when the mutex is recursive.
319 TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t _lock)
320 : unique_lock_t(_mutex, _lock)
321 {}
322
323 // Assumes the calling thread already owns m
324 TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t _lock)
325 : unique_lock_t(_mutex, _lock)
326 {}
327
328public:
329 //------------------------------------------------------------------------//
330 // Backwards compatibility versions (constructor with pointer to mutex)
331 //------------------------------------------------------------------------//
333 : unique_lock_t(*_mutex, std::defer_lock)
334 {
335 // call termination-safe locking. if serial, this call has no effect
336 _lock_deferred();
337 }
338
339 TemplateAutoLock(mutex_type* _mutex, std::defer_lock_t _lock) noexcept
340 : unique_lock_t(*_mutex, _lock)
341 {}
342
343 TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t _lock)
344 : unique_lock_t(*_mutex, _lock)
345 {}
346
347 TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t _lock)
348 : unique_lock_t(*_mutex, _lock)
349 {}
350
351private:
352// helpful macros
353#define _is_stand_mutex(Tp) (std::is_same<Tp, Mutex>::value)
354#define _is_recur_mutex(Tp) (std::is_same<Tp, RecursiveMutex>::value)
355#define _is_other_mutex(Tp) (!_is_stand_mutex(Tp) && !_is_recur_mutex(Tp))
356
357 template <typename Tp = MutexT,
358 typename std::enable_if<_is_stand_mutex(Tp), int>::type = 0>
359 std::string GetTypeString()
360 {
361 return "AutoLock<Mutex>";
362 }
363
364 template <typename Tp = MutexT,
365 typename std::enable_if<_is_recur_mutex(Tp), int>::type = 0>
366 std::string GetTypeString()
367 {
368 return "AutoLock<RecursiveMutex>";
369 }
370
371 template <typename Tp = MutexT,
372 typename std::enable_if<_is_other_mutex(Tp), int>::type = 0>
373 std::string GetTypeString()
374 {
375 return "AutoLock<UNKNOWN_MUTEX>";
376 }
377
378// pollution is bad
379#undef _is_stand_mutex
380#undef _is_recur_mutex
381#undef _is_other_mutex
382
383 // used in _lock_deferred chrono variants to avoid ununsed-variable warning
384 template <typename Tp>
385 void suppress_unused_variable(const Tp&)
386 {}
387
388 //========================================================================//
389 // NOTE on _lock_deferred(...) variants:
390 // a system_error in lock means that the mutex is unavailable
391 // we want to throw the error that comes from locking an unavailable
392 // mutex so that we know there is a memory leak
393 // if the mutex is valid, this will hold until the other thread
394 // finishes
395
396 // sometimes certain destructors use locks, this isn't an issue unless
397 // the object is leaked. When this occurs, the application finalization
398 // (i.e. the real or implied "return 0" part of main) will call destructors
399 // on Tasking object after some static mutex variables are deleted, leading
400 // to the error code (typically on Clang compilers):
401 // libc++abi.dylib: terminating with uncaught exception of type
402 // std::__1::system_error: mutex lock failed: Invalid argument
403 // this function protects against this failure until such a time that
404 // these issues have been resolved
405
406 //========================================================================//
407 // standard locking
408 inline void _lock_deferred()
409 {
410 try
411 {
412 this->unique_lock_t::lock();
413 } catch(std::system_error& e)
414 {
415 PrintLockErrorMessage(e);
416 }
417 }
418
419 //========================================================================//
420 // Tries to lock the associated mutex by calling
421 // m.try_lock_for(_timeout_duration). Blocks until specified
422 // _timeout_duration has elapsed or the lock is acquired, whichever comes
423 // first. May block for longer than _timeout_duration.
424 template <typename Rep, typename Period>
425 void _lock_deferred(const std::chrono::duration<Rep, Period>& _timeout_duration)
426 {
427 try
428 {
429 this->unique_lock_t::try_lock_for(_timeout_duration);
430 } catch(std::system_error& e)
431 {
432 PrintLockErrorMessage(e);
433 }
434 }
435
436 //========================================================================//
437 // Tries to lock the associated mutex by calling
438 // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
439 // been reached or the lock is acquired, whichever comes first. May block
440 // for longer than until _timeout_time has been reached.
441 template <typename Clock, typename Duration>
442 void _lock_deferred(const std::chrono::time_point<Clock, Duration>& _timeout_time)
443 {
444 try
445 {
446 this->unique_lock_t::try_lock_until(_timeout_time);
447 } catch(std::system_error& e)
448 {
449 PrintLockErrorMessage(e);
450 }
451 }
452
453 //========================================================================//
454 // the message for what mutex lock fails due to deleted static mutex
455 // at termination
456 void PrintLockErrorMessage(std::system_error& e)
457 {
458 // use std::cout/std::endl to avoid include dependencies
459 using std::cout;
460 using std::endl;
461 // the error that comes from locking an unavailable mutex
462#if defined(VERBOSE)
463 cout << "Non-critical error: mutex lock failure in "
464 << GetTypeString<mutex_type>() << ". "
465 << "If the app is terminating, Tasking failed to "
466 << "delete an allocated resource and a Tasking destructor is "
467 << "being called after the statics were destroyed. \n\t--> "
468 << "Exception: [code: " << e.code() << "] caught: " << e.what() << std::endl;
469#else
470 suppress_unused_variable(e);
471#endif
472 }
473};
474
475// -------------------------------------------------------------------------- //
476//
477// Use the non-template types below:
478// - AutoLock with Mutex
479// - RecursiveAutoLock with RecursiveMutex
480//
481// -------------------------------------------------------------------------- //
482
485
486// provide abbriviated type if another mutex type is desired to be used
487// aside from above
488template <typename Tp>
490
491} // namespace PTL
#define _is_other_mutex(Tp)
Definition: AutoLock.hh:355
#define _is_stand_mutex(Tp)
Definition: AutoLock.hh:353
#define _is_recur_mutex(Tp)
Definition: AutoLock.hh:354
TemplateAutoLock(mutex_type *_mutex, std::try_to_lock_t _lock)
Definition: AutoLock.hh:343
TemplateAutoLock(mutex_type &_mutex, std::defer_lock_t _lock) noexcept
Definition: AutoLock.hh:312
TemplateAutoLock(mutex_type *_mutex)
Definition: AutoLock.hh:332
unique_lock_t::mutex_type mutex_type
Definition: AutoLock.hh:267
TemplateAutoLock< MutexT > this_type
Definition: AutoLock.hh:266
TemplateAutoLock(mutex_type &_mutex, std::try_to_lock_t _lock)
Definition: AutoLock.hh:319
TemplateAutoLock(mutex_type &_mutex, const std::chrono::duration< Rep, Period > &_timeout_duration)
Definition: AutoLock.hh:290
TemplateAutoLock(mutex_type *_mutex, std::defer_lock_t _lock) noexcept
Definition: AutoLock.hh:339
std::unique_lock< MutexT > unique_lock_t
Definition: AutoLock.hh:265
TemplateAutoLock(mutex_type *_mutex, std::adopt_lock_t _lock)
Definition: AutoLock.hh:347
TemplateAutoLock(mutex_type &_mutex, std::adopt_lock_t _lock)
Definition: AutoLock.hh:324
TemplateAutoLock(mutex_type &_mutex)
Definition: AutoLock.hh:278
TemplateAutoLock(mutex_type &_mutex, const std::chrono::time_point< Clock, Duration > &_timeout_time)
Definition: AutoLock.hh:303
Definition: AutoLock.hh:254
TemplateAutoLock< RecursiveMutex > RecursiveAutoLock
Definition: AutoLock.hh:484
TemplateAutoLock< Mutex > AutoLock
Definition: AutoLock.hh:483