1 /** 2 Mutexes, semaphores and condition variables. 3 4 Copyright: Sean Kelly 2005 - 2009. 5 Copyright: Guillaume Piolat 2016 - 2018. 6 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 */ 8 // This contains part of druntime's core.sys.mutex, core.sys.semaphore core.sys.condition and 9 // Modified to make it @nogc nothrow 10 module dplug.core.sync; 11 12 import core.time; 13 import core.atomic; 14 15 import dplug.core.vec; 16 import dplug.core.nogc; 17 18 import core.stdc.stdio; 19 import core.stdc.stdlib: malloc, free; 20 21 // emulate conditions variable on Windows 22 // (this was originally in Phobos for XP compatibility) 23 version = emulateCondVarWin32; 24 25 version (OSX) 26 version = Darwin; 27 else version (iOS) 28 version = Darwin; 29 else version (TVOS) 30 version = Darwin; 31 else version (WatchOS) 32 version = Darwin; 33 34 version( Windows ) 35 { 36 import core.sys.windows.windows; 37 38 extern (Windows) nothrow @nogc 39 { 40 void InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION * lpCriticalSection, DWORD dwSpinCount); 41 } 42 } 43 else version( Darwin ) 44 { 45 import core.sys.posix.pthread; 46 import core.sync.config; 47 import core.stdc.errno; 48 import core.sys.posix.time; 49 static if (__VERSION__ < 2084) 50 import core.sys.osx.mach.semaphore; // was removed with DMDFE 2.084 51 else 52 import core.sys.darwin.mach.semaphore; 53 } 54 else version( Posix ) 55 { 56 import core.sync.config; 57 import core.stdc.errno; 58 import core.sys.posix.pthread; 59 import core.sys.posix.semaphore; 60 import core.sys.posix.time; 61 } 62 else 63 { 64 static assert(false, "Platform not supported"); 65 } 66 67 68 // 69 // MUTEX 70 // 71 72 /// Returns: A newly created `UnchekedMutex`. 73 UncheckedMutex makeMutex() nothrow @nogc 74 { 75 return UncheckedMutex(42); 76 } 77 78 private enum PosixMutexAlignment = 64; // Wild guess, no measurements done 79 80 // Cargo-culting the spin-count in WTF::Lock 81 // See: https://webkit.org/blog/6161/locking-in-webkit/ 82 private enum CriticalSectionSpinCount = 40; 83 84 struct UncheckedMutex 85 { 86 nothrow @nogc: 87 88 private this(int dummyArg) 89 { 90 assert(!_created); 91 lazyThreadSafeInitialization(); 92 } 93 94 ~this() 95 { 96 // RACE CONDITION HERE 97 // TODO: not easy to make the mutex destruction thread-safe, because one of the 98 // thread must wait. Ignore that for now. 99 100 void* mutexHandle = atomicLoad(_mutex); // Can be the mutex handle, or null. 101 102 if (mutexHandle !is null) 103 { 104 // Now, the mutex could have been destroyed by another thread already. 105 // Make a cas to ensure we are first to attempt it. 106 107 if (cas(&_mutex, &mutexHandle, null)) 108 { 109 destroyMutex(mutexHandle); 110 } 111 } 112 113 _created = 0; 114 } 115 116 @disable this(this); 117 118 /// Lock mutex, and create it in a thread-safe way if it doens't exist yet. 119 /// This is useful when globals access must be protected by a mutex. 120 void lockLazy() @trusted 121 { 122 lazyThreadSafeInitialization(); 123 lock(); 124 } 125 126 /// Lock mutex 127 void lock() 128 { 129 // No sync needed, else would have used lockLazy() 130 version( Windows ) 131 { 132 EnterCriticalSection( cast(CRITICAL_SECTION*) _mutex ); 133 } 134 else version( Posix ) 135 { 136 assumeNothrowNoGC( 137 (pthread_mutex_t* handle) 138 { 139 int res = pthread_mutex_lock(handle); 140 if (res != 0) 141 assert(false); 142 })(handleAddr()); 143 } 144 } 145 146 // undocumented function for internal use 147 void unlock() 148 { 149 version( Windows ) 150 { 151 LeaveCriticalSection( cast(CRITICAL_SECTION*) _mutex ); 152 } 153 else version( Posix ) 154 { 155 assumeNothrowNoGC( 156 (pthread_mutex_t* handle) 157 { 158 int res = pthread_mutex_unlock(handle); 159 if (res != 0) 160 assert(false); 161 })(handleAddr()); 162 } 163 } 164 165 bool tryLock() 166 { 167 version( Windows ) 168 { 169 return TryEnterCriticalSection( cast(CRITICAL_SECTION*) _mutex ) != 0; 170 } 171 else version( Posix ) 172 { 173 int result = assumeNothrowNoGC( 174 (pthread_mutex_t* handle) 175 { 176 return pthread_mutex_trylock(handle); 177 })(handleAddr()); 178 return result == 0; 179 } 180 } 181 182 private: 183 // on Windows, this is a CRITICAL_SECTION*. 184 // else, this is a pthread_mutex_t* 185 void* _mutex; 186 187 // Work-around for Issue 16636 188 // https://issues.dlang.org/show_bug.cgi?id=16636 189 // Still crash with LDC somehow 190 long _created = 0; 191 192 static void* createMutex() 193 { 194 version( Windows ) 195 { 196 CRITICAL_SECTION* critSec = cast(CRITICAL_SECTION*) malloc(CRITICAL_SECTION.sizeof); 197 InitializeCriticalSectionAndSpinCount( critSec, CriticalSectionSpinCount ); 198 return critSec; 199 } 200 else version( Posix ) 201 { 202 pthread_mutex_t* pmtx = cast(pthread_mutex_t*)( alignedMalloc(pthread_mutex_t.sizeof, PosixMutexAlignment) ); 203 204 assumeNothrowNoGC( 205 (pthread_mutex_t* handle) 206 { 207 pthread_mutexattr_t attr = void; 208 pthread_mutexattr_init( &attr ); 209 pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); 210 211 version (Darwin) 212 { 213 // Note: disabled since this breaks thread pool. 214 /+ 215 // OSX mutexes are fair by default, but this has a cost for contended locks 216 // Disable fairness. 217 // https://blog.mozilla.org/nfroyd/2017/03/29/on-mutex-performance-part-1/ 218 enum _PTHREAD_MUTEX_POLICY_FIRSTFIT = 2; 219 pthread_mutexattr_setpolicy_np(& attr, _PTHREAD_MUTEX_POLICY_FIRSTFIT); 220 +/ 221 } 222 223 pthread_mutex_init( handle, &attr ); 224 225 })(pmtx); 226 return pmtx; 227 } 228 else 229 static assert(false); 230 } 231 232 static void destroyMutex(void* mutex) 233 { 234 if (mutex is null) 235 return; 236 237 // TODO: this should be thread-safe, for example a cas with local variable name or _created 238 239 version( Windows ) 240 { 241 DeleteCriticalSection( cast(CRITICAL_SECTION*) mutex ); 242 free(mutex); 243 } 244 else version( Posix ) 245 { 246 assumeNothrowNoGC( 247 (pthread_mutex_t* handle) 248 { 249 pthread_mutex_destroy(handle); 250 })( cast(pthread_mutex_t*) mutex ); 251 alignedFree(mutex, PosixMutexAlignment); 252 } 253 else 254 static assert(false); 255 } 256 257 void lazyThreadSafeInitialization() @trusted 258 { 259 // Is there an existing mutex already? 260 if (atomicLoad(_mutex) !is null) 261 return; 262 263 // Create one mutex. 264 void* mtx = createMutex(); 265 void* p = cast(void*)mtx; 266 void** here = &_mutex; 267 void* ifThis = null; 268 269 // Try to set _mutex. 270 if (!cas(here, &ifThis, p)) 271 { 272 // Another thread created _mutex first. Destroy our useless instance. 273 destroyMutex(mtx); 274 } 275 } 276 277 package: 278 version( Posix ) 279 { 280 pthread_mutex_t* handleAddr() nothrow @nogc 281 { 282 return cast(pthread_mutex_t*) _mutex; 283 } 284 } 285 } 286 287 unittest 288 { 289 UncheckedMutex mutex = makeMutex(); 290 foreach(i; 0..100) 291 { 292 mutex.lock(); 293 mutex.unlock(); 294 295 if (mutex.tryLock) 296 mutex.unlock(); 297 } 298 mutex.destroy(); 299 } 300 301 unittest 302 { 303 __gshared UncheckedMutex mutex2; 304 mutex2.lockLazy(); 305 mutex2.unlock(); 306 } 307 308 309 310 // 311 // SEMAPHORE 312 // 313 314 /// Returns: A newly created `UncheckedSemaphore` 315 UncheckedSemaphore makeSemaphore(uint count) nothrow @nogc 316 { 317 return UncheckedSemaphore(count); 318 } 319 320 struct UncheckedSemaphore 321 { 322 private this( uint count ) nothrow @nogc 323 { 324 version( Windows ) 325 { 326 m_hndl = CreateSemaphoreA( null, count, int.max, null ); 327 if( m_hndl == m_hndl.init ) 328 assert(false); 329 } 330 else version( Darwin ) 331 { 332 mach_port_t task = assumeNothrowNoGC( 333 () 334 { 335 return mach_task_self(); 336 })(); 337 338 kern_return_t rc = assumeNothrowNoGC( 339 (mach_port_t t, semaphore_t* handle, uint count) 340 { 341 return semaphore_create(t, handle, SYNC_POLICY_FIFO, count ); 342 })(task, &m_hndl, count); 343 344 if( rc ) 345 assert(false); 346 } 347 else version( Posix ) 348 { 349 int rc = sem_init( &m_hndl, 0, count ); 350 if( rc ) 351 assert(false); 352 } 353 _created = 1; 354 } 355 356 ~this() nothrow @nogc 357 { 358 if (_created) 359 { 360 version( Windows ) 361 { 362 BOOL rc = CloseHandle( m_hndl ); 363 assert( rc, "Unable to destroy semaphore" ); 364 } 365 else version( Darwin ) 366 { 367 mach_port_t task = assumeNothrowNoGC( 368 () 369 { 370 return mach_task_self(); 371 })(); 372 373 kern_return_t rc = assumeNothrowNoGC( 374 (mach_port_t t, semaphore_t handle) 375 { 376 return semaphore_destroy( t, handle ); 377 })(task, m_hndl); 378 379 assert( !rc, "Unable to destroy semaphore" ); 380 } 381 else version( Posix ) 382 { 383 int rc = sem_destroy( &m_hndl ); 384 assert( !rc, "Unable to destroy semaphore" ); 385 } 386 _created = 0; 387 } 388 } 389 390 @disable this(this); 391 392 void wait() nothrow @nogc 393 { 394 version( Windows ) 395 { 396 DWORD rc = WaitForSingleObject( m_hndl, INFINITE ); 397 assert( rc == WAIT_OBJECT_0 ); 398 } 399 else version( Darwin ) 400 { 401 while( true ) 402 { 403 auto rc = assumeNothrowNoGC( 404 (semaphore_t handle) 405 { 406 return semaphore_wait(handle); 407 })(m_hndl); 408 if( !rc ) 409 return; 410 if( rc == KERN_ABORTED && errno == EINTR ) 411 continue; 412 assert(false); 413 } 414 } 415 else version( Posix ) 416 { 417 while( true ) 418 { 419 if (!assumeNothrowNoGC( 420 (sem_t* handle) 421 { 422 return sem_wait(handle); 423 })(&m_hndl)) 424 return; 425 if( errno != EINTR ) 426 assert(false); 427 } 428 } 429 } 430 431 bool wait( Duration period ) nothrow @nogc 432 { 433 assert( !period.isNegative ); 434 435 version( Windows ) 436 { 437 auto maxWaitMillis = dur!("msecs")( uint.max - 1 ); 438 439 while( period > maxWaitMillis ) 440 { 441 auto rc = WaitForSingleObject( m_hndl, cast(uint) 442 maxWaitMillis.total!"msecs" ); 443 switch( rc ) 444 { 445 case WAIT_OBJECT_0: 446 return true; 447 case WAIT_TIMEOUT: 448 period -= maxWaitMillis; 449 continue; 450 default: 451 assert(false); 452 } 453 } 454 switch( WaitForSingleObject( m_hndl, cast(uint) period.total!"msecs" ) ) 455 { 456 case WAIT_OBJECT_0: 457 return true; 458 case WAIT_TIMEOUT: 459 return false; 460 default: 461 assert(false); 462 } 463 } 464 else version( Darwin ) 465 { 466 mach_timespec_t t = void; 467 (cast(byte*) &t)[0 .. t.sizeof] = 0; 468 469 if( period.total!"seconds" > t.tv_sec.max ) 470 { 471 t.tv_sec = t.tv_sec.max; 472 t.tv_nsec = cast(typeof(t.tv_nsec)) period.split!("seconds", "nsecs")().nsecs; 473 } 474 else 475 period.split!("seconds", "nsecs")(t.tv_sec, t.tv_nsec); 476 while( true ) 477 { 478 auto rc = assumeNothrowNoGC( 479 (semaphore_t handle, mach_timespec_t t) 480 { 481 return semaphore_timedwait(handle, t); 482 })(m_hndl, t); 483 if( !rc ) 484 return true; 485 if( rc == KERN_OPERATION_TIMED_OUT ) 486 return false; 487 if( rc != KERN_ABORTED || errno != EINTR ) 488 assert(false); 489 } 490 } 491 else version( Posix ) 492 { 493 timespec t; 494 495 assumeNothrowNoGC( 496 (ref timespec t, Duration period) 497 { 498 mktspec( t, period ); 499 })(t, period); 500 501 while( true ) 502 { 503 if (! ((sem_t* handle, timespec* t) 504 { 505 return sem_timedwait(handle, t); 506 })(&m_hndl, &t)) 507 return true; 508 if( errno == ETIMEDOUT ) 509 return false; 510 if( errno != EINTR ) 511 assert(false); 512 } 513 } 514 } 515 516 void notify() nothrow @nogc 517 { 518 version( Windows ) 519 { 520 if( !ReleaseSemaphore( m_hndl, 1, null ) ) 521 assert(false); 522 } 523 else version( Darwin ) 524 { 525 auto rc = assumeNothrowNoGC( 526 (semaphore_t handle) 527 { 528 return semaphore_signal(handle); 529 })(m_hndl); 530 if( rc ) 531 assert(false); 532 } 533 else version( Posix ) 534 { 535 int rc = sem_post( &m_hndl ); 536 if( rc ) 537 assert(false); 538 } 539 } 540 541 bool tryWait() nothrow @nogc 542 { 543 version( Windows ) 544 { 545 switch( WaitForSingleObject( m_hndl, 0 ) ) 546 { 547 case WAIT_OBJECT_0: 548 return true; 549 case WAIT_TIMEOUT: 550 return false; 551 default: 552 assert(false); 553 } 554 } 555 else version( Darwin ) 556 { 557 return wait( dur!"hnsecs"(0) ); 558 } 559 else version( Posix ) 560 { 561 while( true ) 562 { 563 if( !sem_trywait( &m_hndl ) ) 564 return true; 565 if( errno == EAGAIN ) 566 return false; 567 if( errno != EINTR ) 568 assert(false); 569 } 570 } 571 } 572 573 574 private: 575 version( Windows ) 576 { 577 HANDLE m_hndl; 578 } 579 else version( Darwin ) 580 { 581 semaphore_t m_hndl; 582 } 583 else version( Posix ) 584 { 585 sem_t m_hndl; 586 } 587 ulong _created = 0; 588 } 589 590 591 unittest 592 { 593 foreach(j; 0..4) 594 { 595 UncheckedSemaphore semaphore = makeSemaphore(1); 596 foreach(i; 0..100) 597 { 598 semaphore.wait(); 599 semaphore.notify(); 600 if (semaphore.tryWait()) 601 semaphore.notify(); 602 } 603 } 604 } 605 606 607 608 // 609 // CONDITION VARIABLE 610 // 611 612 613 ConditionVariable makeConditionVariable() nothrow @nogc 614 { 615 return ConditionVariable(42); 616 } 617 618 /** 619 * This struct represents a condition variable as conceived by C.A.R. Hoare. As 620 * per Mesa type monitors however, "signal" has been replaced with "notify" to 621 * indicate that control is not transferred to the waiter when a notification 622 * is sent. 623 */ 624 struct ConditionVariable 625 { 626 public: 627 nothrow: 628 @nogc: 629 630 /// Initializes a condition variable. 631 this(int dummy) 632 { 633 version( Windows ) 634 { 635 m_blockLock = CreateSemaphoreA( null, 1, 1, null ); 636 if( m_blockLock == m_blockLock.init ) 637 assert(false); 638 m_blockQueue = CreateSemaphoreA( null, 0, int.max, null ); 639 if( m_blockQueue == m_blockQueue.init ) 640 assert(false); 641 642 m_unblockLock = cast(CRITICAL_SECTION*) malloc(CRITICAL_SECTION.sizeof); 643 InitializeCriticalSectionAndSpinCount( m_unblockLock, CriticalSectionSpinCount ); 644 } 645 else version( Posix ) 646 { 647 _handle = cast(pthread_cond_t*)( alignedMalloc(pthread_cond_t.sizeof, PosixMutexAlignment) ); 648 649 int rc = pthread_cond_init( handleAddr(), null ); 650 if( rc ) 651 assert(false); 652 } 653 } 654 655 656 ~this() 657 { 658 version( Windows ) 659 { 660 CloseHandle( m_blockLock ); 661 CloseHandle( m_blockQueue ); 662 if (m_unblockLock !is null) 663 { 664 DeleteCriticalSection( m_unblockLock ); 665 free(m_unblockLock); 666 m_unblockLock = null; 667 } 668 } 669 else version( Posix ) 670 { 671 if (_handle !is null) 672 { 673 int rc = pthread_cond_destroy( handleAddr() ); 674 assert( !rc, "Unable to destroy condition" ); 675 alignedFree(_handle, PosixMutexAlignment); 676 _handle = null; 677 } 678 } 679 } 680 681 /// Wait until notified. 682 /// The associated mutex should always be the same for this condition variable. 683 void wait(UncheckedMutex* assocMutex) 684 { 685 version( Windows ) 686 { 687 timedWait( INFINITE, assocMutex ); 688 } 689 else version( Posix ) 690 { 691 int rc = pthread_cond_wait( handleAddr(), assocMutex.handleAddr() ); 692 if( rc ) 693 assert(false); 694 } 695 } 696 697 /// Notifies one waiter. 698 void notifyOne() 699 { 700 version( Windows ) 701 { 702 notifyImpl( false ); 703 } 704 else version( Posix ) 705 { 706 int rc = pthread_cond_signal( handleAddr() ); 707 if( rc ) 708 assert(false); 709 } 710 } 711 712 713 /// Notifies all waiters. 714 void notifyAll() 715 { 716 version( Windows ) 717 { 718 notifyImpl( true ); 719 } 720 else version( Posix ) 721 { 722 int rc = pthread_cond_broadcast( handleAddr() ); 723 if( rc ) 724 assert(false); 725 } 726 } 727 728 version(Posix) 729 { 730 pthread_cond_t* handleAddr() nothrow @nogc 731 { 732 return _handle; 733 } 734 } 735 736 737 private: 738 version( Windows ) 739 { 740 bool timedWait( DWORD timeout, UncheckedMutex* assocMutex ) 741 { 742 int numSignalsLeft; 743 int numWaitersGone; 744 DWORD rc; 745 746 rc = WaitForSingleObject( m_blockLock, INFINITE ); 747 assert( rc == WAIT_OBJECT_0 ); 748 749 m_numWaitersBlocked++; 750 751 rc = ReleaseSemaphore( m_blockLock, 1, null ); 752 assert( rc ); 753 754 assocMutex.unlock(); 755 756 rc = WaitForSingleObject( m_blockQueue, timeout ); 757 assert( rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT ); 758 bool timedOut = (rc == WAIT_TIMEOUT); 759 760 EnterCriticalSection( m_unblockLock ); 761 762 if( (numSignalsLeft = m_numWaitersToUnblock) != 0 ) 763 { 764 if ( timedOut ) 765 { 766 // timeout (or canceled) 767 if( m_numWaitersBlocked != 0 ) 768 { 769 m_numWaitersBlocked--; 770 // do not unblock next waiter below (already unblocked) 771 numSignalsLeft = 0; 772 } 773 else 774 { 775 // spurious wakeup pending!! 776 m_numWaitersGone = 1; 777 } 778 } 779 if( --m_numWaitersToUnblock == 0 ) 780 { 781 if( m_numWaitersBlocked != 0 ) 782 { 783 // open the gate 784 rc = ReleaseSemaphore( m_blockLock, 1, null ); 785 assert( rc ); 786 // do not open the gate below again 787 numSignalsLeft = 0; 788 } 789 else if( (numWaitersGone = m_numWaitersGone) != 0 ) 790 { 791 m_numWaitersGone = 0; 792 } 793 } 794 } 795 else if( ++m_numWaitersGone == int.max / 2 ) 796 { 797 // timeout/canceled or spurious event :-) 798 rc = WaitForSingleObject( m_blockLock, INFINITE ); 799 assert( rc == WAIT_OBJECT_0 ); 800 // something is going on here - test of timeouts? 801 m_numWaitersBlocked -= m_numWaitersGone; 802 rc = ReleaseSemaphore( m_blockLock, 1, null ); 803 assert( rc == WAIT_OBJECT_0 ); 804 m_numWaitersGone = 0; 805 } 806 807 LeaveCriticalSection( m_unblockLock ); 808 809 if( numSignalsLeft == 1 ) 810 { 811 // better now than spurious later (same as ResetEvent) 812 for( ; numWaitersGone > 0; --numWaitersGone ) 813 { 814 rc = WaitForSingleObject( m_blockQueue, INFINITE ); 815 assert( rc == WAIT_OBJECT_0 ); 816 } 817 // open the gate 818 rc = ReleaseSemaphore( m_blockLock, 1, null ); 819 assert( rc ); 820 } 821 else if( numSignalsLeft != 0 ) 822 { 823 // unblock next waiter 824 rc = ReleaseSemaphore( m_blockQueue, 1, null ); 825 assert( rc ); 826 } 827 assocMutex.lock(); 828 return !timedOut; 829 } 830 831 832 void notifyImpl( bool all ) 833 { 834 DWORD rc; 835 836 EnterCriticalSection( m_unblockLock ); 837 838 if( m_numWaitersToUnblock != 0 ) 839 { 840 if( m_numWaitersBlocked == 0 ) 841 { 842 LeaveCriticalSection( m_unblockLock ); 843 return; 844 } 845 if( all ) 846 { 847 m_numWaitersToUnblock += m_numWaitersBlocked; 848 m_numWaitersBlocked = 0; 849 } 850 else 851 { 852 m_numWaitersToUnblock++; 853 m_numWaitersBlocked--; 854 } 855 LeaveCriticalSection( m_unblockLock ); 856 } 857 else if( m_numWaitersBlocked > m_numWaitersGone ) 858 { 859 rc = WaitForSingleObject( m_blockLock, INFINITE ); 860 assert( rc == WAIT_OBJECT_0 ); 861 if( 0 != m_numWaitersGone ) 862 { 863 m_numWaitersBlocked -= m_numWaitersGone; 864 m_numWaitersGone = 0; 865 } 866 if( all ) 867 { 868 m_numWaitersToUnblock = m_numWaitersBlocked; 869 m_numWaitersBlocked = 0; 870 } 871 else 872 { 873 m_numWaitersToUnblock = 1; 874 m_numWaitersBlocked--; 875 } 876 LeaveCriticalSection( m_unblockLock ); 877 rc = ReleaseSemaphore( m_blockQueue, 1, null ); 878 assert( rc ); 879 } 880 else 881 { 882 LeaveCriticalSection( m_unblockLock ); 883 } 884 } 885 886 887 // NOTE: This implementation uses Algorithm 8c as described here: 888 // http://groups.google.com/group/comp.programming.threads/ 889 // browse_frm/thread/1692bdec8040ba40/e7a5f9d40e86503a 890 HANDLE m_blockLock; // auto-reset event (now semaphore) 891 HANDLE m_blockQueue; // auto-reset event (now semaphore) 892 CRITICAL_SECTION* m_unblockLock = null; // internal mutex/CS 893 int m_numWaitersGone = 0; 894 int m_numWaitersBlocked = 0; 895 int m_numWaitersToUnblock = 0; 896 } 897 else version( Posix ) 898 { 899 pthread_cond_t* _handle; 900 } 901 } 902 903 unittest 904 { 905 import dplug.core.thread; 906 907 auto mutex = makeMutex(); 908 auto condvar = makeConditionVariable(); 909 910 bool finished = false; 911 912 // Launch a thread that wait on this condition 913 Thread t = launchInAThread( 914 () { 915 mutex.lock(); 916 while(!finished) 917 condvar.wait(&mutex); 918 mutex.unlock(); 919 }); 920 921 // Notify termination 922 mutex.lock(); 923 finished = true; 924 mutex.unlock(); 925 condvar.notifyOne(); 926 927 t.join(); 928 }