Geant4 11.1.1
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
G4AutoLock.hh
Go to the documentation of this file.
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
10// * *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
17// * *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
25//
26// G4Autolock
27//
28// Class Description:
29//
30// This class provides a mechanism to create a mutex and locks/unlocks it.
31// Can be used by applications to implement in a portable way a mutexing logic.
32// Usage Example:
33//
34// #include "G4Threading.hh"
35// #include "G4AutoLock.hh"
36//
37// // defined somewhere -- static so all threads see the same mutex
38// static G4Mutex aMutex;
39//
40// // somewhere else:
41// // The G4AutoLock instance will automatically unlock the mutex when it
42// // goes out of scope. One typically defines the scope within { } if
43// // there is thread-safe code following the auto-lock
44//
45// {
46// G4AutoLock l(&aMutex);
47// ProtectedCode();
48// }
49//
50// UnprotectedCode();
51//
52// // When ProtectedCode() is calling a function that also tries to lock
53// // a normal G4AutoLock + G4Mutex will "deadlock". In other words, the
54// // the mutex in the ProtectedCode() function will wait forever to
55// // acquire the lock that is being held by the function that called
56// // ProtectedCode(). In this situation, use a G4RecursiveAutoLock +
57// // G4RecursiveMutex, e.g.
58//
59// // defined somewhere -- static so all threads see the same mutex
60// static G4RecursiveMutex aRecursiveMutex;
61//
62// // this function is sometimes called directly and sometimes called
63// // from SomeFunction_B(), which also locks the mutex
64// void SomeFunction_A()
65// {
66// // when called from SomeFunction_B(), a G4Mutex + G4AutoLock will
67// // deadlock
68// G4RecursiveAutoLock l(&aRecursiveMutex);
69// // do something
70// }
71//
72// void SomeFunction_B()
73// {
74//
75// {
76// G4RecursiveAutoLock l(&aRecursiveMutex);
77// SomeFunction_A();
78// }
79//
80// UnprotectedCode();
81// }
82//
83
84// --------------------------------------------------------------------
85// Author: Andrea Dotti (15 Feb 2013): First Implementation
86//
87// Update: Jonathan Madsen (9 Feb 2018): Replaced custom implementation
88// with inheritance from C++11 unique_lock, which inherits the
89// following member functions:
90//
91// - unique_lock(unique_lock&& other) noexcept;
92// - explicit unique_lock(mutex_type& m);
93// - unique_lock(mutex_type& m, std::defer_lock_t t) noexcept;
94// - unique_lock(mutex_type& m, std::try_to_lock_t t);
95// - unique_lock(mutex_type& m, std::adopt_lock_t t);
96//
97// - template <typename Rep, typename Period>
98// unique_lock(mutex_type& m,
99// const std::chrono::duration<Rep,Period>& timeout_duration);
100//
101// - template<typename Clock, typename Duration>
102// unique_lock(mutex_type& m,
103// const std::chrono::time_point<Clock,Duration>& timeout_time);
104//
105// - void lock();
106// - void unlock();
107// - bool try_lock();
108//
109// - template <typename Rep, typename Period>
110// bool try_lock_for(const std::chrono::duration<Rep,Period>&);
111//
112// - template <typename Rep, typename Period>
113// bool try_lock_until(const std::chrono::time_point<Clock,Duration>&);
114//
115// - void swap(unique_lock& other) noexcept;
116// - mutex_type* release() noexcept;
117// - mutex_type* mutex() const noexcept;
118// - bool owns_lock() const noexcept;
119// - explicit operator bool() const noexcept;
120// - unique_lock& operator=(unique_lock&& other);
121//
122// --------------------------------------------------------------------
123//
124// Note that G4AutoLock is defined also for a sequential Geant4 build but below
125// regarding implementation (also found in G4Threading.hh)
126//
127//
128// NOTE ON GEANT4 SERIAL BUILDS AND MUTEX/UNIQUE_LOCK
129// ==================================================
130//
131// G4Mutex and G4RecursiveMutex are always C++11 std::mutex types
132// however, in serial mode, using G4MUTEXLOCK and G4MUTEXUNLOCK on these
133// types has no effect -- i.e. the mutexes are not actually locked or unlocked
134//
135// Additionally, when a G4Mutex or G4RecursiveMutex is used with G4AutoLock
136// and G4RecursiveAutoLock, respectively, these classes also suppressing
137// the locking and unlocking of the mutex. Regardless of the build type,
138// G4AutoLock and G4RecursiveAutoLock inherit from std::unique_lock<std::mutex>
139// and std::unique_lock<std::recursive_mutex>, respectively. This means
140// that in situations (such as is needed by the analysis category), the
141// G4AutoLock and G4RecursiveAutoLock can be passed to functions requesting
142// a std::unique_lock. Within these functions, since std::unique_lock
143// member functions are not virtual, they will not retain the dummy locking
144// and unlocking behavior
145// --> An example of this behavior can be found below
146//
147// Jonathan R. Madsen (February 21, 2018)
148//
149/**
150
151//============================================================================//
152
153void print_threading()
154{
155#ifdef G4MULTITHREADED
156 std::cout << "\nUsing G4MULTITHREADED version..." << std::endl;
157#else
158 std::cout << "\nUsing G4SERIAL version..." << std::endl;
159#endif
160}
161
162//============================================================================//
163
164typedef std::unique_lock<std::mutex> unique_lock_t;
165// functions for casting G4AutoLock to std::unique_lock to demonstrate
166// that G4AutoLock is NOT polymorphic
167void as_unique_lock(unique_lock_t* lock) { lock->lock(); }
168void as_unique_unlock(unique_lock_t* lock) { lock->unlock(); }
169
170//============================================================================//
171
172void run(const uint64_t& n)
173{
174 // sync the threads a bit
175 std::this_thread::sleep_for(std::chrono::milliseconds(10));
176
177 // get two mutexes to avoid deadlock when l32 actually locks
178 G4AutoLock l32(G4TypeMutex<int32_t>(), std::defer_lock);
179 G4AutoLock l64(G4TypeMutex<int64_t>(), std::defer_lock);
180
181 // when serial: will not execute std::unique_lock::lock() because
182 // it overrides the member function
183 l32.lock();
184 // regardless of serial or MT: will execute std::unique_lock::lock()
185 // because std::unique_lock::lock() is not virtual
186 as_unique_lock(&l64);
187
188 std::cout << "Running iteration " << n << "..." << std::endl;
189}
190
191//============================================================================//
192// execute some work
193template <typename thread_type = std::thread>
194void exec(uint64_t n)
195{
196 // get two mutexes to avoid deadlock when l32 actually locks
197 G4AutoLock l32(G4TypeMutex<int32_t>(), std::defer_lock);
198 G4AutoLock l64(G4TypeMutex<int64_t>(), std::defer_lock);
199
200 std::vector<thread_type*> threads(n, nullptr);
201 for(uint64_t i = 0; i < n; ++i)
202 {
203 threads[i] = new thread_type();
204 *(threads[i]) = std::move(thread_type(run, i));
205 }
206
207 // when serial: will not execute std::unique_lock::lock() because
208 // it overrides the member function
209 l32.lock();
210 // regardless of serial or MT: will execute std::unique_lock::lock()
211 // because std::unique_lock::lock() is not virtual
212 as_unique_lock(&l64);
213
214 std::cout << "Joining..." << std::endl;
215
216 // when serial: will not execute std::unique_lock::unlock() because
217 // it overrides the member function
218 l32.unlock();
219 // regardless of serial or MT: will execute std::unique_lock::unlock()
220 // because std::unique_lock::unlock() is not virtual
221 as_unique_unlock(&l64);
222
223 // NOTE ABOUT UNLOCKS:
224 // in MT, commenting out either
225 // l32.unlock();
226 // or
227 // as_unique_unlock(&l64);
228 // creates a deadlock; in serial, commenting out
229 // as_unique_unlock(&l64);
230 // creates a deadlock but commenting out
231 // l32.unlock();
232 // does not
233
234 // clean up and join
235 for(uint64_t i = 0; i < n; ++i)
236 {
237 threads[i]->join();
238 delete threads[i];
239 }
240 threads.clear();
241}
242
243//============================================================================//
244
245int main()
246{
247 print_threading();
248
249 uint64_t n = 30;
250 std::cout << "\nRunning with real threads...\n" << std::endl;
251 exec<std::thread>(n);
252 std::cout << "\nRunning with fake threads...\n" << std::endl;
253 exec<G4DummyThread>(n);
254
255}
256
257**/
258// --------------------------------------------------------------------
259#ifndef G4AUTOLOCK_HH
260#define G4AUTOLOCK_HH
261
262#include "G4Threading.hh"
263
264#include <chrono>
265#include <iostream>
266#include <mutex>
267#include <system_error>
268
269// Note: Note that G4TemplateAutoLock by itself is not thread-safe and
270// cannot be shared among threads due to the locked switch
271//
272template <typename _Mutex_t>
273class G4TemplateAutoLock : public std::unique_lock<_Mutex_t>
274{
275 public:
276 //------------------------------------------------------------------------//
277 // Some useful typedefs
278 //------------------------------------------------------------------------//
279 using unique_lock_t = std::unique_lock<_Mutex_t>;
281 using mutex_type = typename unique_lock_t::mutex_type;
282
283 public:
284 //------------------------------------------------------------------------//
285 // STL-consistent reference form constructors
286 //------------------------------------------------------------------------//
287
288 // reference form is consistent with STL lock_guard types
289 // Locks the associated mutex by calling m.lock(). The behavior is
290 // undefined if the current thread already owns the mutex except when
291 // the mutex is recursive
293 : unique_lock_t(_mutex, std::defer_lock)
294 {
295 // call termination-safe locking. if serial, this call has no effect
296 _lock_deferred();
297 }
298
299 // Tries to lock the associated mutex by calling
300 // m.try_lock_for(_timeout_duration). Blocks until specified
301 // _timeout_duration has elapsed or the lock is acquired, whichever comes
302 // first. May block for longer than _timeout_duration.
303 template <typename Rep, typename Period>
305 mutex_type& _mutex,
306 const std::chrono::duration<Rep, Period>& _timeout_duration)
307 : unique_lock_t(_mutex, std::defer_lock)
308 {
309 // call termination-safe locking. if serial, this call has no effect
310 _lock_deferred(_timeout_duration);
311 }
312
313 // Tries to lock the associated mutex by calling
314 // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
315 // been reached or the lock is acquired, whichever comes first. May block
316 // for longer than until _timeout_time has been reached.
317 template <typename Clock, typename Duration>
319 mutex_type& _mutex,
320 const std::chrono::time_point<Clock, Duration>& _timeout_time)
321 : unique_lock_t(_mutex, std::defer_lock)
322 {
323 // call termination-safe locking. if serial, this call has no effect
324 _lock_deferred(_timeout_time);
325 }
326
327 // Does not lock the associated mutex.
328 G4TemplateAutoLock(mutex_type& _mutex, std::defer_lock_t _lock) noexcept
329 : unique_lock_t(_mutex, _lock)
330 {}
331
332#ifdef G4MULTITHREADED
333
334 // Tries to lock the associated mutex without blocking by calling
335 // m.try_lock(). The behavior is undefined if the current thread already
336 // owns the mutex except when the mutex is recursive.
337 G4TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t _lock)
338 : unique_lock_t(_mutex, _lock)
339 {}
340
341 // Assumes the calling thread already owns m
342 G4TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t _lock)
343 : unique_lock_t(_mutex, _lock)
344 {}
345
346#else
347
348 // serial dummy version (initializes unique_lock but does not lock)
349 G4TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t)
350 : unique_lock_t(_mutex, std::defer_lock)
351 {}
352
353 // serial dummy version (initializes unique_lock but does not lock)
354 G4TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t)
355 : unique_lock_t(_mutex, std::defer_lock)
356 {}
357
358#endif // defined(G4MULTITHREADED)
359
360 public:
361 //------------------------------------------------------------------------//
362 // Backwards compatibility versions (constructor with pointer to mutex)
363 //------------------------------------------------------------------------//
365 : unique_lock_t(*_mutex, std::defer_lock)
366 {
367 // call termination-safe locking. if serial, this call has no effect
368 _lock_deferred();
369 }
370
371 G4TemplateAutoLock(mutex_type* _mutex, std::defer_lock_t _lock) noexcept
372 : unique_lock_t(*_mutex, _lock)
373 {}
374
375#if defined(G4MULTITHREADED)
376
377 G4TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t _lock)
378 : unique_lock_t(*_mutex, _lock)
379 {}
380
381 G4TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t _lock)
382 : unique_lock_t(*_mutex, _lock)
383 {}
384
385#else // NOT defined(G4MULTITHREADED) -- i.e. serial
386
387 G4TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t)
388 : unique_lock_t(*_mutex, std::defer_lock)
389 {}
390
391 G4TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t)
392 : unique_lock_t(*_mutex, std::defer_lock)
393 {}
394
395#endif // defined(G4MULTITHREADED)
396
397 public:
398 //------------------------------------------------------------------------//
399 // Non-constructor overloads
400 //------------------------------------------------------------------------//
401
402#if defined(G4MULTITHREADED)
403
404 // overload nothing
405
406#else // NOT defined(G4MULTITHREADED) -- i.e. serial
407
408 // override unique lock member functions to keep from locking/unlocking
409 // but does not override in polymorphic usage
410 void lock() {}
411 void unlock() {}
412 bool try_lock() { return true; }
413
414 template <typename Rep, typename Period>
415 bool try_lock_for(const std::chrono::duration<Rep, Period>&)
416 {
417 return true;
418 }
419
420 template <typename Clock, typename Duration>
421 bool try_lock_until(const std::chrono::time_point<Clock, Duration>&)
422 {
423 return true;
424 }
425
426 void swap(this_type& other) noexcept { std::swap(*this, other); }
427 bool owns_lock() const noexcept { return false; }
428
429 // no need to overload
430 // explicit operator bool() const noexcept;
431 // this_type& operator=(this_type&& other);
432 // mutex_type* release() noexcept;
433 // mutex_type* mutex() const noexcept;
434
435#endif // defined(G4MULTITHREADED)
436
437 private:
438// helpful macros
439#define _is_stand_mutex(_Tp) (std::is_same<_Tp, G4Mutex>::value)
440#define _is_recur_mutex(_Tp) (std::is_same<_Tp, G4RecursiveMutex>::value)
441#define _is_other_mutex(_Tp) (!_is_stand_mutex(_Tp) && !_is_recur_mutex(_Tp))
442
443 template <typename _Tp = _Mutex_t,
444 typename std::enable_if<_is_stand_mutex(_Tp), int>::type = 0>
445 std::string GetTypeString()
446 {
447 return "G4AutoLock<G4Mutex>";
448 }
449
450 template <typename _Tp = _Mutex_t,
451 typename std::enable_if<_is_recur_mutex(_Tp), int>::type = 0>
452 std::string GetTypeString()
453 {
454 return "G4AutoLock<G4RecursiveMutex>";
455 }
456
457 template <typename _Tp = _Mutex_t,
458 typename std::enable_if<_is_other_mutex(_Tp), int>::type = 0>
459 std::string GetTypeString()
460 {
461 return "G4AutoLock<UNKNOWN_MUTEX>";
462 }
463
464// pollution is bad
465#undef _is_stand_mutex
466#undef _is_recur_mutex
467#undef _is_other_mutex
468
469 // used in _lock_deferred chrono variants to avoid ununsed-variable warning
470 template <typename _Tp>
471 void suppress_unused_variable(const _Tp&)
472 {}
473
474 //========================================================================//
475 // NOTE on _lock_deferred(...) variants:
476 // a system_error in lock means that the mutex is unavailable
477 // we want to throw the error that comes from locking an unavailable
478 // mutex so that we know there is a memory leak
479 // if the mutex is valid, this will hold until the other thread
480 // finishes
481
482 // sometimes certain destructors use locks, this isn't an issue unless
483 // the object is leaked. When this occurs, the application finalization
484 // (i.e. the real or implied "return 0" part of main) will call destructors
485 // on Geant4 object after some static mutex variables are deleted, leading
486 // to the error code (typically on Clang compilers):
487 // libc++abi.dylib: terminating with uncaught exception of type
488 // std::__1::system_error: mutex lock failed: Invalid argument
489 // this function protects against this failure until such a time that
490 // these issues have been resolved
491
492 //========================================================================//
493 // standard locking
494 inline void _lock_deferred()
495 {
496#if defined(G4MULTITHREADED)
497 try
498 {
499 this->unique_lock_t::lock();
500 } catch(std::system_error& e)
501 {
502 PrintLockErrorMessage(e);
503 }
504#endif
505 }
506
507 //========================================================================//
508 // Tries to lock the associated mutex by calling
509 // m.try_lock_for(_timeout_duration). Blocks until specified
510 // _timeout_duration has elapsed or the lock is acquired, whichever comes
511 // first. May block for longer than _timeout_duration.
512 template <typename Rep, typename Period>
513 void _lock_deferred(
514 const std::chrono::duration<Rep, Period>& _timeout_duration)
515 {
516#if defined(G4MULTITHREADED)
517 try
518 {
519 this->unique_lock_t::try_lock_for(_timeout_duration);
520 } catch(std::system_error& e)
521 {
522 PrintLockErrorMessage(e);
523 }
524#else
525 suppress_unused_variable(_timeout_duration);
526#endif
527 }
528
529 //========================================================================//
530 // Tries to lock the associated mutex by calling
531 // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
532 // been reached or the lock is acquired, whichever comes first. May block
533 // for longer than until _timeout_time has been reached.
534 template <typename Clock, typename Duration>
535 void _lock_deferred(
536 const std::chrono::time_point<Clock, Duration>& _timeout_time)
537 {
538#if defined(G4MULTITHREADED)
539 try
540 {
541 this->unique_lock_t::try_lock_until(_timeout_time);
542 } catch(std::system_error& e)
543 {
544 PrintLockErrorMessage(e);
545 }
546#else
547 suppress_unused_variable(_timeout_time);
548#endif
549 }
550
551 //========================================================================//
552 // the message for what mutex lock fails due to deleted static mutex
553 // at termination
554 void PrintLockErrorMessage(std::system_error& e)
555 {
556 // use std::cout/std::endl to avoid include dependencies
557 using std::cout;
558 using std::endl;
559 // the error that comes from locking an unavailable mutex
560#if defined(G4VERBOSE)
561 cout << "Non-critical error: mutex lock failure in "
562 << GetTypeString<mutex_type>() << ". "
563 << "If the app is terminating, Geant4 failed to "
564 << "delete an allocated resource and a Geant4 destructor is "
565 << "being called after the statics were destroyed. \n\t--> "
566 << "Exception: [code: " << e.code() << "] caught: " << e.what()
567 << endl;
568#else
569 suppress_unused_variable(e);
570#endif
571 }
572};
573
574// -------------------------------------------------------------------------- //
575//
576// Use the non-template types below:
577// - G4AutoLock with G4Mutex
578// - G4RecursiveAutoLock with G4RecursiveMutex
579//
580// -------------------------------------------------------------------------- //
581
584
585// provide abbriviated type if another mutex type is desired to be used
586// aside from above
587template <typename _Tp>
589
590#endif // G4AUTOLOCK_HH
#define _is_recur_mutex(_Tp)
Definition: G4AutoLock.hh:440
#define _is_stand_mutex(_Tp)
Definition: G4AutoLock.hh:439
#define _is_other_mutex(_Tp)
Definition: G4AutoLock.hh:441
G4TemplateAutoLock(mutex_type *_mutex, std::adopt_lock_t)
Definition: G4AutoLock.hh:391
G4TemplateAutoLock(mutex_type &_mutex, std::adopt_lock_t)
Definition: G4AutoLock.hh:354
std::unique_lock< _Mutex_t > unique_lock_t
Definition: G4AutoLock.hh:279
G4TemplateAutoLock(mutex_type *_mutex, std::defer_lock_t _lock) noexcept
Definition: G4AutoLock.hh:371
bool try_lock_until(const std::chrono::time_point< Clock, Duration > &)
Definition: G4AutoLock.hh:421
void swap(this_type &other) noexcept
Definition: G4AutoLock.hh:426
G4TemplateAutoLock(mutex_type &_mutex, std::try_to_lock_t)
Definition: G4AutoLock.hh:349
G4TemplateAutoLock(mutex_type *_mutex, std::try_to_lock_t)
Definition: G4AutoLock.hh:387
G4TemplateAutoLock(mutex_type &_mutex, const std::chrono::duration< Rep, Period > &_timeout_duration)
Definition: G4AutoLock.hh:304
G4TemplateAutoLock(mutex_type &_mutex)
Definition: G4AutoLock.hh:292
bool try_lock_for(const std::chrono::duration< Rep, Period > &)
Definition: G4AutoLock.hh:415
bool owns_lock() const noexcept
Definition: G4AutoLock.hh:427
G4TemplateAutoLock(mutex_type &_mutex, const std::chrono::time_point< Clock, Duration > &_timeout_time)
Definition: G4AutoLock.hh:318
G4TemplateAutoLock(mutex_type *_mutex)
Definition: G4AutoLock.hh:364
typename unique_lock_t::mutex_type mutex_type
Definition: G4AutoLock.hh:281
G4TemplateAutoLock(mutex_type &_mutex, std::defer_lock_t _lock) noexcept
Definition: G4AutoLock.hh:328