Geant4-11
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//
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
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
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>
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 {
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 {
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 {
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
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
void suppress_unused_variable(const Tp &)
Definition: AutoLock.hh:385
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::string GetTypeString()
Definition: AutoLock.hh:359
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
void _lock_deferred(const std::chrono::duration< Rep, Period > &_timeout_duration)
Definition: AutoLock.hh:425
TemplateAutoLock(mutex_type &_mutex, const std::chrono::time_point< Clock, Duration > &_timeout_time)
Definition: AutoLock.hh:303
void PrintLockErrorMessage(std::system_error &e)
Definition: AutoLock.hh:456
void _lock_deferred(const std::chrono::time_point< Clock, Duration > &_timeout_time)
Definition: AutoLock.hh:442
Definition: AutoLock.hh:254
TemplateAutoLock< RecursiveMutex > RecursiveAutoLock
Definition: AutoLock.hh:484
TemplateAutoLock< Mutex > AutoLock
Definition: AutoLock.hh:483