Geant4 Cross Reference |
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 149 typedef 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 152 void as_unique_lock(unique_lock_t* lock) { lock->lock(); } 153 void as_unique_unlock(unique_lock_t* lock) { lock->unlock(); } 154 155 //======================================================================================// 156 157 void 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 178 template <typename thread_type = std::thread> 179 void 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 230 int 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/ConsumeParameters.hh" 247 #include "PTL/Types.hh" 248 249 #include <chrono> 250 #include <iostream> 251 #include <mutex> 252 #include <system_error> 253 254 namespace 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 // 259 template <typename MutexT> 260 class TemplateAutoLock : public std::unique_lock<MutexT> 261 { 262 public: 263 //------------------------------------------------------------------------// 264 // Some useful typedefs 265 //------------------------------------------------------------------------// 266 using unique_lock_t = std::unique_lock<MutexT>; 267 using this_type = TemplateAutoLock<MutexT>; 268 using mutex_type = typename unique_lock_t::mutex_type; 269 270 public: 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> 291 TemplateAutoLock(mutex_type& _mutex, 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> 304 TemplateAutoLock(mutex_type& _mutex, 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 329 public: 330 //------------------------------------------------------------------------// 331 // Backwards compatibility versions (constructor with pointer to mutex) 332 //------------------------------------------------------------------------// 333 TemplateAutoLock(mutex_type* _mutex) 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 352 private: 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 466 ConsumeParameters(e); 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 479 using AutoLock = TemplateAutoLock<Mutex>; 480 using RecursiveAutoLock = TemplateAutoLock<RecursiveMutex>; 481 482 } // namespace PTL 483