1.. _module-pw_sync: 2 3======= 4pw_sync 5======= 6The ``pw_sync`` module contains utilities for synchronizing between threads 7and/or interrupts through signaling primitives and critical section lock 8primitives. 9 10.. Warning:: 11 12 This module is still under construction, the API is not yet stable. 13 14.. Note:: 15 16 The objects in this module do not have an Init() style public API which is 17 common in many RTOS C APIs. Instead, they rely on being able to invoke the 18 native initialization APIs for synchronization primitives during C++ 19 construction. 20 21 In order to support global statically constructed synchronization without 22 constexpr constructors, the user and/or backend **MUST** ensure that any 23 initialization required in your environment is done prior to the creation 24 and/or initialization of the native synchronization primitives 25 (e.g. kernel initialization). 26 27-------------------------------- 28Critical Section Lock Primitives 29-------------------------------- 30The critical section lock primitives provided by this module comply with 31`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_, 32`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and where 33relevant 34`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_ C++ 35named requirements. This means that they are compatible with existing helpers in 36the STL's ``<mutex>`` thread support library. For example `std::lock_guard 37<https://en.cppreference.com/w/cpp/thread/lock_guard>`_ and `std::unique_lock 38<https://en.cppreference.com/w/cpp/thread/unique_lock>`_ can be directly used. 39 40Mutex 41===== 42The Mutex is a synchronization primitive that can be used to protect shared data 43from being simultaneously accessed by multiple threads. It offers exclusive, 44non-recursive ownership semantics where priority inheritance is used to solve 45the classic priority-inversion problem. 46 47The Mutex's API is C++11 STL 48`std::mutex <https://en.cppreference.com/w/cpp/thread/mutex>`_ like, 49meaning it is a 50`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_ 51and `Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_. 52 53.. list-table:: 54 :header-rows: 1 55 56 * - Supported on 57 - Backend module 58 * - FreeRTOS 59 - :ref:`module-pw_sync_freertos` 60 * - ThreadX 61 - :ref:`module-pw_sync_threadx` 62 * - embOS 63 - :ref:`module-pw_sync_embos` 64 * - STL 65 - :ref:`module-pw_sync_stl` 66 * - Baremetal 67 - Planned 68 * - Zephyr 69 - Planned 70 * - CMSIS-RTOS API v2 & RTX5 71 - Planned 72 73C++ 74--- 75.. doxygenclass:: pw::sync::Mutex 76 :members: 77 78.. cpp:namespace-push:: pw::sync::Mutex 79 80.. list-table:: 81 :header-rows: 1 82 :widths: 70 10 10 10 83 84 * - Safe to use in context 85 - Thread 86 - Interrupt 87 - NMI 88 * - :cpp:class:`pw::sync::Mutex::Mutex` 89 - ✔ 90 - 91 - 92 * - :cpp:func:`pw::sync::Mutex::~Mutex` 93 - ✔ 94 - 95 - 96 * - :cpp:func:`lock` 97 - ✔ 98 - 99 - 100 * - :cpp:func:`try_lock` 101 - ✔ 102 - 103 - 104 * - :cpp:func:`unlock` 105 - ✔ 106 - 107 - 108 109.. cpp:namespace-pop:: 110 111 112Examples in C++ 113^^^^^^^^^^^^^^^ 114.. code-block:: cpp 115 116 #include "pw_sync/mutex.h" 117 118 pw::sync::Mutex mutex; 119 120 void ThreadSafeCriticalSection() { 121 mutex.lock(); 122 NotThreadSafeCriticalSection(); 123 mutex.unlock(); 124 } 125 126 127Alternatively you can use C++'s RAII helpers to ensure you always unlock. 128 129.. code-block:: cpp 130 131 #include <mutex> 132 133 #include "pw_sync/mutex.h" 134 135 pw::sync::Mutex mutex; 136 137 void ThreadSafeCriticalSection() { 138 std::lock_guard lock(mutex); 139 NotThreadSafeCriticalSection(); 140 } 141 142C 143- 144The Mutex must be created in C++, however it can be passed into C using the 145``pw_sync_Mutex`` opaque struct alias. 146 147.. doxygenfunction:: pw_sync_Mutex_Lock 148.. doxygenfunction:: pw_sync_Mutex_TryLock 149.. doxygenfunction:: pw_sync_Mutex_Unlock 150 151.. list-table:: 152 :header-rows: 1 153 :widths: 70 10 10 10 154 155 * - Safe to use in context 156 - Thread 157 - Interrupt 158 - NMI 159 * - ``void pw_sync_Mutex_Lock`` 160 - ✔ 161 - 162 - 163 * - ``bool pw_sync_Mutex_TryLock`` 164 - ✔ 165 - 166 - 167 * - ``void pw_sync_Mutex_Unlock`` 168 - ✔ 169 - 170 - 171 172Example in C 173^^^^^^^^^^^^ 174.. code-block:: cpp 175 176 #include "pw_sync/mutex.h" 177 178 pw::sync::Mutex mutex; 179 180 extern pw_sync_Mutex mutex; // This can only be created in C++. 181 182 void ThreadSafeCriticalSection(void) { 183 pw_sync_Mutex_Lock(&mutex); 184 NotThreadSafeCriticalSection(); 185 pw_sync_Mutex_Unlock(&mutex); 186 } 187 188TimedMutex 189========== 190.. cpp:namespace-push:: pw::sync 191 192The :cpp:class:`TimedMutex` is an extension of the Mutex which offers timeout 193and deadline based semantics. 194 195The :cpp:class:`TimedMutex`'s API is C++11 STL 196`std::timed_mutex <https://en.cppreference.com/w/cpp/thread/timed_mutex>`_ like, 197meaning it is a 198`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_, 199`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and 200`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_. 201 202Note that the :cpp:class:`TimedMutex` is a derived :cpp:class:`Mutex` class, 203meaning that a :cpp:class:`TimedMutex` can be used by someone who needs the 204basic :cpp:class:`Mutex`. This is in contrast to the C++ STL's 205`std::timed_mutex <https://en.cppreference.com/w/cpp/thread/timed_mutex>`_. 206 207.. cpp:namespace-pop:: 208 209.. list-table:: 210 :header-rows: 1 211 212 * - Supported on 213 - Backend module 214 * - FreeRTOS 215 - :ref:`module-pw_sync_freertos` 216 * - ThreadX 217 - :ref:`module-pw_sync_threadx` 218 * - embOS 219 - :ref:`module-pw_sync_embos` 220 * - STL 221 - :ref:`module-pw_sync_stl` 222 * - Zephyr 223 - Planned 224 * - CMSIS-RTOS API v2 & RTX5 225 - Planned 226 227C++ 228--- 229.. doxygenclass:: pw::sync::TimedMutex 230 :members: 231 232.. cpp:namespace-push:: pw::sync::TimedMutex 233 234.. list-table:: 235 :header-rows: 1 236 :widths: 70 10 10 10 237 238 * - Safe to use in context 239 - Thread 240 - Interrupt 241 - NMI 242 * - :cpp:class:`pw::sync::TimedMutex::TimedMutex` 243 - ✔ 244 - 245 - 246 * - :cpp:func:`pw::sync::TimedMutex::~TimedMutex` 247 - ✔ 248 - 249 - 250 * - :cpp:func:`pw::sync::Mutex::lock` 251 - ✔ 252 - 253 - 254 * - :cpp:func:`pw::sync::Mutex::try_lock` 255 - ✔ 256 - 257 - 258 * - :cpp:func:`try_lock_for` 259 - ✔ 260 - 261 - 262 * - :cpp:func:`try_lock_until` 263 - ✔ 264 - 265 - 266 * - :cpp:func:`pw::sync::Mutex::unlock` 267 - ✔ 268 - 269 - 270 271.. cpp:namespace-pop:: 272 273 274Examples in C++ 275^^^^^^^^^^^^^^^ 276.. code-block:: cpp 277 278 #include "pw_chrono/system_clock.h" 279 #include "pw_sync/timed_mutex.h" 280 281 pw::sync::TimedMutex mutex; 282 283 bool ThreadSafeCriticalSectionWithTimeout( 284 const SystemClock::duration timeout) { 285 if (!mutex.try_lock_for(timeout)) { 286 return false; 287 } 288 NotThreadSafeCriticalSection(); 289 mutex.unlock(); 290 return true; 291 } 292 293Alternatively you can use C++'s RAII helpers to ensure you always unlock. 294 295.. code-block:: cpp 296 297 #include <mutex> 298 299 #include "pw_chrono/system_clock.h" 300 #include "pw_sync/timed_mutex.h" 301 302 pw::sync::TimedMutex mutex; 303 304 bool ThreadSafeCriticalSectionWithTimeout( 305 const SystemClock::duration timeout) { 306 std::unique_lock lock(mutex, std::defer_lock); 307 if (!lock.try_lock_for(timeout)) { 308 return false; 309 } 310 NotThreadSafeCriticalSection(); 311 return true; 312 } 313 314C 315- 316The TimedMutex must be created in C++, however it can be passed into C using the 317``pw_sync_TimedMutex`` opaque struct alias. 318 319.. doxygenfile:: timed_mutex.h 320 :sections: func 321 322.. list-table:: 323 :header-rows: 1 324 :widths: 70 10 10 10 325 326 * - Safe to use in context 327 - Thread 328 - Interrupt 329 - NMI 330 * - :cpp:func:`pw_sync_TimedMutex_Lock` 331 - ✔ 332 - 333 - 334 * - :cpp:func:`pw_sync_TimedMutex_TryLock` 335 - ✔ 336 - 337 - 338 * - :cpp:func:`pw_sync_TimedMutex_TryLockFor` 339 - ✔ 340 - 341 - 342 * - :cpp:func:`pw_sync_TimedMutex_TryLockUntil` 343 - ✔ 344 - 345 - 346 * - :cpp:func:`pw_sync_TimedMutex_Unlock` 347 - ✔ 348 - 349 - 350 351Example in C 352^^^^^^^^^^^^ 353.. code-block:: cpp 354 355 #include "pw_chrono/system_clock.h" 356 #include "pw_sync/timed_mutex.h" 357 358 pw::sync::TimedMutex mutex; 359 360 extern pw_sync_TimedMutex mutex; // This can only be created in C++. 361 362 bool ThreadSafeCriticalSectionWithTimeout( 363 const pw_chrono_SystemClock_Duration timeout) { 364 if (!pw_sync_TimedMutex_TryLockFor(&mutex, timeout)) { 365 return false; 366 } 367 NotThreadSafeCriticalSection(); 368 pw_sync_TimedMutex_Unlock(&mutex); 369 return true; 370 } 371 372RecursiveMutex 373============== 374``pw_sync`` provides ``pw::sync::RecursiveMutex``, a recursive mutex 375implementation. At this time, this facade can only be used internally by 376Pigweed. 377 378InterruptSpinLock 379================= 380The InterruptSpinLock is a synchronization primitive that can be used to protect 381shared data from being simultaneously accessed by multiple threads and/or 382interrupts as a targeted global lock, with the exception of Non-Maskable 383Interrupts (NMIs). It offers exclusive, non-recursive ownership semantics where 384IRQs up to a backend defined level of "NMIs" will be masked to solve 385priority-inversion. 386 387This InterruptSpinLock relies on built-in local interrupt masking to make it 388interrupt safe without requiring the caller to separately mask and unmask 389interrupts when using this primitive. 390 391Unlike global interrupt locks, this also works safely and efficiently on SMP 392systems. On systems which are not SMP, spinning is not required but some state 393may still be used to detect recursion. 394 395The InterruptSpinLock is a 396`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_ 397and 398`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_. 399 400.. list-table:: 401 :header-rows: 1 402 403 * - Supported on 404 - Backend module 405 * - FreeRTOS 406 - :ref:`module-pw_sync_freertos` 407 * - ThreadX 408 - :ref:`module-pw_sync_threadx` 409 * - embOS 410 - :ref:`module-pw_sync_embos` 411 * - STL 412 - :ref:`module-pw_sync_stl` 413 * - Baremetal 414 - Planned, not ready for use 415 * - Zephyr 416 - Planned 417 * - CMSIS-RTOS API v2 & RTX5 418 - Planned 419 420C++ 421--- 422.. doxygenclass:: pw::sync::InterruptSpinLock 423 :members: 424 425.. cpp:namespace-push:: pw::sync::InterruptSpinLock 426 427.. list-table:: 428 :widths: 70 10 10 10 429 :header-rows: 1 430 431 * - Safe to use in context 432 - Thread 433 - Interrupt 434 - NMI 435 * - :cpp:class:`pw::sync::InterruptSpinLock::InterruptSpinLock` 436 - ✔ 437 - ✔ 438 - 439 * - :cpp:func:`pw::sync::InterruptSpinLock::~InterruptSpinLock` 440 - ✔ 441 - ✔ 442 - 443 * - :cpp:func:`lock` 444 - ✔ 445 - ✔ 446 - 447 * - :cpp:func:`try_lock` 448 - ✔ 449 - ✔ 450 - 451 * - :cpp:func:`unlock` 452 - ✔ 453 - ✔ 454 - 455 456.. cpp:namespace-pop:: 457 458Examples in C++ 459^^^^^^^^^^^^^^^ 460.. code-block:: cpp 461 462 #include "pw_sync/interrupt_spin_lock.h" 463 464 pw::sync::InterruptSpinLock interrupt_spin_lock; 465 466 void InterruptSafeCriticalSection() { 467 interrupt_spin_lock.lock(); 468 NotThreadSafeCriticalSection(); 469 interrupt_spin_lock.unlock(); 470 } 471 472 473Alternatively you can use C++'s RAII helpers to ensure you always unlock. 474 475.. code-block:: cpp 476 477 #include <mutex> 478 479 #include "pw_sync/interrupt_spin_lock.h" 480 481 pw::sync::InterruptSpinLock interrupt_spin_lock; 482 483 void InterruptSafeCriticalSection() { 484 std::lock_guard lock(interrupt_spin_lock); 485 NotThreadSafeCriticalSection(); 486 } 487 488 489C 490- 491The InterruptSpinLock must be created in C++, however it can be passed into C using the 492``pw_sync_InterruptSpinLock`` opaque struct alias. 493 494.. doxygenfunction:: pw_sync_InterruptSpinLock_Lock 495.. doxygenfunction:: pw_sync_InterruptSpinLock_TryLock 496.. doxygenfunction:: pw_sync_InterruptSpinLock_Unlock 497 498.. list-table:: 499 :widths: 70 10 10 10 500 :header-rows: 1 501 502 * - Safe to use in context 503 - Thread 504 - Interrupt 505 - NMI 506 * - :cpp:func:`pw_sync_InterruptSpinLock_Lock` 507 - ✔ 508 - ✔ 509 - 510 * - :cpp:func:`pw_sync_InterruptSpinLock_TryLock` 511 - ✔ 512 - ✔ 513 - 514 * - :cpp:func:`pw_sync_InterruptSpinLock_Unlock` 515 - ✔ 516 - ✔ 517 - 518 519Example in C 520^^^^^^^^^^^^ 521.. code-block:: cpp 522 523 #include "pw_chrono/system_clock.h" 524 #include "pw_sync/interrupt_spin_lock.h" 525 526 pw::sync::InterruptSpinLock interrupt_spin_lock; 527 528 extern pw_sync_InterruptSpinLock interrupt_spin_lock; // This can only be created in C++. 529 530 void InterruptSafeCriticalSection(void) { 531 pw_sync_InterruptSpinLock_Lock(&interrupt_spin_lock); 532 NotThreadSafeCriticalSection(); 533 pw_sync_InterruptSpinLock_Unlock(&interrupt_spin_lock); 534 } 535 536Thread Safety Lock Annotations 537============================== 538Pigweed's critical section lock primitives support Clang's thread safety 539analysis extension for C++. The analysis is completely static at compile-time. 540This is only supported when building with Clang. The annotations are no-ops when 541using different compilers. 542 543Pigweed provides the ``pw_sync/lock_annotations.h`` header file with macro 544definitions to allow developers to document the locking policies of 545multi-threaded code. The annotations can also help program analysis tools to 546identify potential thread safety issues. 547 548More information on Clang's thread safety analysis system can be found 549`here <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_. 550 551Enabling Clang's Analysis 552------------------------- 553In order to enable the analysis, Clang requires that the ``-Wthread-safety`` 554compilation flag be used. In addition, if any STL components like 555``std::lock_guard`` are used, the STL's built in annotations have to be manually 556enabled, typically by setting the ``_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS`` 557macro. 558 559If using GN, the ``pw_build:clang_thread_safety_warnings`` config is provided 560to do this for you, when added to your clang toolchain definition's default 561configs. 562 563Why use lock annotations? 564------------------------- 565Lock annotations can help warn you about potential race conditions in your code 566when using locks: you have to remember to grab lock(s) before entering a 567critical section, yuou have to remember to unlock it when you leave, and you 568have to avoid deadlocks. 569 570Clang's lock annotations let you inform the compiler and anyone reading your 571code which variables are guarded by which locks, which locks should or cannot be 572held when calling which function, which order locks should be acquired in, etc. 573 574Using Lock Annotations 575---------------------- 576When referring to locks in the arguments of the attributes, you should 577use variable names or more complex expressions (e.g. ``my_object->lock_``) 578that evaluate to a concrete lock object whenever possible. If the lock 579you want to refer to is not in scope, you may use a member pointer 580(e.g. ``&MyClass::lock_``) to refer to a lock in some (unknown) object. 581 582Annotating Lock Usage 583^^^^^^^^^^^^^^^^^^^^^ 584.. doxygendefine:: PW_GUARDED_BY 585.. doxygendefine:: PW_PT_GUARDED_BY 586.. doxygendefine:: PW_ACQUIRED_AFTER 587.. doxygendefine:: PW_ACQUIRED_BEFORE 588.. doxygendefine:: PW_EXCLUSIVE_LOCKS_REQUIRED 589.. doxygendefine:: PW_SHARED_LOCKS_REQUIRED 590.. doxygendefine:: PW_LOCKS_EXCLUDED 591.. doxygendefine:: PW_LOCK_RETURNED 592.. doxygendefine:: PW_LOCKABLE 593.. doxygendefine:: PW_SCOPED_LOCKABLE 594.. doxygendefine:: PW_EXCLUSIVE_LOCK_FUNCTION 595.. doxygendefine:: PW_SHARED_LOCK_FUNCTION 596.. doxygendefine:: PW_UNLOCK_FUNCTION 597.. doxygendefine:: PW_EXCLUSIVE_TRYLOCK_FUNCTION 598.. doxygendefine:: PW_SHARED_TRYLOCK_FUNCTION 599.. doxygendefine:: PW_ASSERT_EXCLUSIVE_LOCK 600.. doxygendefine:: PW_ASSERT_SHARED_LOCK 601.. doxygendefine:: PW_NO_LOCK_SAFETY_ANALYSIS 602 603Annotating Lock Objects 604^^^^^^^^^^^^^^^^^^^^^^^ 605In order of lock usage annotation to work, the lock objects themselves need to 606be annotated as well. In case you are providing your own lock or psuedo-lock 607object, you can use the macros in this section to annotate it. 608 609As an example we've annotated a Lock and a RAII ScopedLocker object for you, see 610the macro documentation after for more details: 611 612.. code-block:: cpp 613 614 class PW_LOCKABLE("Lock") Lock { 615 public: 616 void Lock() PW_EXCLUSIVE_LOCK_FUNCTION(); 617 618 void ReaderLock() PW_SHARED_LOCK_FUNCTION(); 619 620 void Unlock() PW_UNLOCK_FUNCTION(); 621 622 void ReaderUnlock() PW_SHARED_TRYLOCK_FUNCTION(); 623 624 bool TryLock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true); 625 626 bool ReaderTryLock() PW_SHARED_TRYLOCK_FUNCTION(true); 627 628 void AssertHeld() PW_ASSERT_EXCLUSIVE_LOCK(); 629 630 void AssertReaderHeld() PW_ASSERT_SHARED_LOCK(); 631 }; 632 633 634 // Tag types for selecting a constructor. 635 struct adopt_lock_t {} inline constexpr adopt_lock = {}; 636 struct defer_lock_t {} inline constexpr defer_lock = {}; 637 struct shared_lock_t {} inline constexpr shared_lock = {}; 638 639 class PW_SCOPED_LOCKABLE ScopedLocker { 640 // Acquire lock, implicitly acquire *this and associate it with lock. 641 ScopedLocker(Lock *lock) PW_EXCLUSIVE_LOCK_FUNCTION(lock) 642 : lock_(lock), locked(true) { 643 lock->Lock(); 644 } 645 646 // Assume lock is held, implicitly acquire *this and associate it with lock. 647 ScopedLocker(Lock *lock, adopt_lock_t) PW_EXCLUSIVE_LOCKS_REQUIRED(lock) 648 : lock_(lock), locked(true) {} 649 650 // Acquire lock in shared mode, implicitly acquire *this and associate it 651 // with lock. 652 ScopedLocker(Lock *lock, shared_lock_t) PW_SHARED_LOCK_FUNCTION(lock) 653 : lock_(lock), locked(true) { 654 lock->ReaderLock(); 655 } 656 657 // Assume lock is held in shared mode, implicitly acquire *this and associate 658 // it with lock. 659 ScopedLocker(Lock *lock, adopt_lock_t, shared_lock_t) 660 PW_SHARED_LOCKS_REQUIRED(lock) : lock_(lock), locked(true) {} 661 662 // Assume lock is not held, implicitly acquire *this and associate it with 663 // lock. 664 ScopedLocker(Lock *lock, defer_lock_t) PW_LOCKS_EXCLUDED(lock) 665 : lock_(lock), locked(false) {} 666 667 // Release *this and all associated locks, if they are still held. 668 // There is no warning if the scope was already unlocked before. 669 ~ScopedLocker() PW_UNLOCK_FUNCTION() { 670 if (locked) 671 lock_->GenericUnlock(); 672 } 673 674 // Acquire all associated locks exclusively. 675 void Lock() PW_EXCLUSIVE_LOCK_FUNCTION() { 676 lock_->Lock(); 677 locked = true; 678 } 679 680 // Try to acquire all associated locks exclusively. 681 bool TryLock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true) { 682 return locked = lock_->TryLock(); 683 } 684 685 // Acquire all associated locks in shared mode. 686 void ReaderLock() PW_SHARED_LOCK_FUNCTION() { 687 lock_->ReaderLock(); 688 locked = true; 689 } 690 691 // Try to acquire all associated locks in shared mode. 692 bool ReaderTryLock() PW_SHARED_TRYLOCK_FUNCTION(true) { 693 return locked = lock_->ReaderTryLock(); 694 } 695 696 // Release all associated locks. Warn on double unlock. 697 void Unlock() PW_UNLOCK_FUNCTION() { 698 lock_->Unlock(); 699 locked = false; 700 } 701 702 // Release all associated locks. Warn on double unlock. 703 void ReaderUnlock() PW_UNLOCK_FUNCTION() { 704 lock_->ReaderUnlock(); 705 locked = false; 706 } 707 708 private: 709 Lock* lock_; 710 bool locked_; 711 }; 712 713----------------------------- 714Critical Section Lock Helpers 715----------------------------- 716 717Virtual Lock Interfaces 718======================= 719Virtual lock interfaces can be useful when lock selection cannot be templated. 720 721Why use virtual locks? 722---------------------- 723Virtual locks enable depending on locks without templating implementation code 724on the type, while retaining flexibility with respect to the concrete lock type. 725Pigweed tries to avoid pushing policy on to users, and virtual locks are one way 726to accomplish that without templating everything. 727 728A case when virtual locks are useful is when the concrete lock type changes at 729run time. For example, access to flash may be protected at run time by an 730internal mutex, however at crash time we may want to switch to a no-op lock. A 731virtual lock interface could be used here to minimize the code-size cost that 732would occur otherwise if the flash driver were templated. 733 734VirtualBasicLock 735---------------- 736The ``VirtualBasicLock`` interface meets the 737`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_ C++ 738named requirement. Our critical section lock primitives offer optional virtual 739versions, including: 740 741* :cpp:func:`pw::sync::VirtualMutex` 742* :cpp:func:`pw::sync::VirtualTimedMutex` 743* :cpp:func:`pw::sync::VirtualInterruptSpinLock` 744 745Borrowable 746========== 747The Borrowable is a helper construct that enables callers to borrow an object 748which is guarded by a lock, enabling a containerized style of external locking. 749 750Users who need access to the guarded object can ask to acquire a 751``BorrowedPointer`` which permits access while the lock is held. 752 753This class is compatible with locks which comply with 754`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_, 755`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and 756`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_ 757C++ named requirements. 758 759By default the selected lock type is a ``pw::sync::VirtualBasicLockable``. If 760this virtual interface is used, the templated lock parameter can be skipped. 761 762External vs Internal locking 763---------------------------- 764Before we explain why Borrowable is useful, it's important to understand the 765trade-offs when deciding on using internal and/or external locking. 766 767Internal locking is when the lock is hidden from the caller entirely and is used 768internally to the API. For example: 769 770.. code-block:: cpp 771 772 class BankAccount { 773 public: 774 void Deposit(int amount) { 775 std::lock_guard lock(mutex_); 776 balance_ += amount; 777 } 778 779 void Withdraw(int amount) { 780 std::lock_guard lock(mutex_); 781 balance_ -= amount; 782 } 783 784 void Balance() const { 785 std::lock_guard lock(mutex_); 786 return balance_; 787 } 788 789 private: 790 int balance_ PW_GUARDED_BY(mutex_); 791 pw::sync::Mutex mutex_; 792 }; 793 794Internal locking guarantees that any concurrent calls to its public member 795functions don't corrupt an instance of that class. This is typically ensured by 796having each member function acquire a lock on the object upon entry. This way, 797for any instance, there can only be one member function call active at any 798moment, serializing the operations. 799 800One common issue that pops up is that member functions may have to call other 801member functions which also require locks. This typically results in a 802duplication of the public API into an internal mirror where the lock is already 803held. This along with having to modify every thread-safe public member function 804may results in an increased code size. 805 806However, with the per-method locking approach, it is not possible to perform a 807multi-method thread-safe transaction. For example, what if we only wanted to 808withdraw money if the balance was high enough? With the current API there would 809be a risk that money is withdrawn after we've checked the balance. 810 811This is usually why external locking is used. This is when the lock is exposed 812to the caller and may be used externally to the public API. External locking 813can take may forms which may even include mixing internal and external locking. 814In its most simplistic form it is an external lock used along side each 815instance, e.g.: 816 817.. code-block:: cpp 818 819 class BankAccount { 820 public: 821 void Deposit(int amount) { 822 balance_ += amount; 823 } 824 825 void Withdraw(int amount) { 826 balance_ -= amount; 827 } 828 829 void Balance() const { 830 return balance_; 831 } 832 833 private: 834 int balance_; 835 }; 836 837 pw::sync::Mutex bobs_account_mutex; 838 BankAccount bobs_account PW_GUARDED_BY(bobs_account_mutex); 839 840The lock is acquired before the bank account is used for a transaction. In 841addition, we do not have to modify every public function and its trivial to 842call other public member functions from a public member function. However, as 843you can imagine instantiating and passing around the instances and their locks 844can become error prone. 845 846This is why ``Borrowable`` exists. 847 848Why use Borrowable? 849------------------- 850``Borrowable`` offers code-size efficient way to enable external locking that is 851easy and safe to use. It is effectively a container which holds references to a 852protected instance and its lock which provides RAII-style access. 853 854.. code-block:: cpp 855 856 pw::sync::Mutex bobs_account_mutex; 857 BankAccount bobs_account PW_GUARDED_BY(bobs_account_mutex); 858 pw::sync::Borrowable<BankAccount, pw::sync::Mutex> bobs_acount( 859 bobs_account, bobs_account_mutex); 860 861This construct is useful when sharing objects or data which are transactional in 862nature where making individual operations threadsafe is insufficient. See the 863section on internal vs external locking tradeoffs above. 864 865It can also offer a code-size and stack-usage efficient way to separate timeout 866constraints between the acquiring of the shared object and timeouts used for the 867shared object's API. For example, imagine you have an I2c bus which is used by 868several threads and you'd like to specify an ACK timeout of 50ms. It'd be ideal 869if the duration it takes to gain exclusive access to the I2c bus does not eat 870into the ACK timeout you'd like to use for the transaction. Borrowable can help 871you do exactly this if you provide access to the I2c bus through a 872``Borrowable``. 873 874C++ 875--- 876.. doxygenclass:: pw::sync::BorrowedPointer 877 :members: 878 879.. doxygenclass:: pw::sync::Borrowable 880 :members: 881 882Example in C++ 883^^^^^^^^^^^^^^ 884 885.. code-block:: cpp 886 887 #include <chrono> 888 889 #include "pw_bytes/span.h" 890 #include "pw_i2c/initiator.h" 891 #include "pw_status/try.h" 892 #include "pw_status/result.h" 893 #include "pw_sync/borrow.h" 894 #include "pw_sync/mutex.h" 895 896 class ExampleI2c : public pw::i2c::Initiator; 897 898 pw::sync::VirtualMutex i2c_mutex; 899 ExampleI2c i2c; 900 pw::sync::Borrowable<ExampleI2c> borrowable_i2c(i2c, i2c_mutex); 901 902 pw::Result<ConstByteSpan> ReadI2cData(ByteSpan buffer) { 903 // Block indefinitely waiting to borrow the i2c bus. 904 pw::sync::BorrowedPointer<ExampleI2c> borrowed_i2c = 905 borrowable_i2c.acquire(); 906 907 // Execute a sequence of transactions to get the needed data. 908 PW_TRY(borrowed_i2c->WriteFor(kFirstWrite, std::chrono::milliseconds(50))); 909 PW_TRY(borrowed_i2c->WriteReadFor(kSecondWrite, buffer, 910 std::chrono::milliseconds(10))); 911 912 // Borrowed i2c pointer is returned when the scope exits. 913 return buffer; 914 } 915 916InlineBorrowable 917================= 918``InlineBorrowable`` is a helper to simplify the common use case where an object 919is wrapped in a ``Borrowable`` for its entire lifetime. The InlineBorrowable 920owns the guarded object and the lock object. 921 922InlineBorrowable has a separate parameter for the concrete lock type 923that is instantiated and a (possibly virtual) lock interface type that is 924referenced by users of the guarded object. The default lock is 925``pw::sync::VirtualMutex`` and the default lock interface is 926``pw::sync::VirtualBasicLockable``. 927 928An InlineBorrowable is a Borrowable with the same guarded object and lock 929interface types, and it can be passed directly to APIs that expect a Borrowable 930reference. 931 932Why use InlineBorrowable? 933------------------------- 934It is a safer and simpler way to guard an object for its entire lifetime. The 935unguarded object is never exposed and doesn't need to be stored in a separate 936variable or data member. The guarded object and its lock are guaranteed to have 937the same lifetime, and the lock cannot be re-used for any other purpose. 938 939Constructing objects in-place 940----------------------------- 941The guarded object and its lock are constructed in-place by the 942InlineBorrowable, and any constructor parameters required by the object or 943its lock must be passed through the InlineBorrowable constructor. There are 944several ways to do this: 945 946* Pass the parameters for the guarded object inline to the constructor. This is 947 the recommended way to construct the object when the lock does not require any 948 constructor parameters. Use the ``std::in_place`` marker to invoke the inline 949 constructor. 950 951 .. code-block:: cpp 952 953 InlineBorrowable<Foo> foo(std::in_place, foo_arg1, foo_arg2); 954 InlineBorrowable<std::array<int, 2>> foo_array(std::in_place, 1, 2); 955 956* Pass the parameters inside tuples: 957 958 .. code-block:: cpp 959 960 InlineBorrowable<Foo> foo(std::forward_as_tuple(foo_arg1, foo_arg2)); 961 962 InlineBorrowable<Foo, MyLock> foo_lock( 963 std::forward_as_tuple(foo_arg1, foo_arg2), 964 std::forward_as_tuple(lock_arg1, lock_arg2)); 965 966 .. note:: This approach only supports list initialization starting with C++20. 967 968* Use callables to construct the guarded object and lock object: 969 970 .. code-block:: cpp 971 972 InlineBorrowable<Foo> foo([&]{ return Foo{foo_arg1, foo_arg2}; }); 973 974 InlineBorrowable<Foo, MyLock> foo_lock( 975 [&]{ return Foo{foo_arg1, foo_arg2}; } 976 [&]{ return MyLock{lock_arg1, lock_arg2}; } 977 978 .. note:: It is possible to construct and return objects that are not copyable 979 or movable, thanks to mandatory copy ellision (return value optimization). 980 981C++ 982--- 983.. doxygenclass:: pw::sync::InlineBorrowable 984 :members: 985 986Example in C++ 987^^^^^^^^^^^^^^ 988.. code-block:: cpp 989 990 #include <utility> 991 992 #include "pw_bytes/span.h" 993 #include "pw_i2c/initiator.h" 994 #include "pw_status/result.h" 995 #include "pw_sync/inline_borrowable.h" 996 997 struct I2cOptions; 998 999 class ExampleI2c : public pw::i2c::Initiator { 1000 public: 1001 ExampleI2c(int bus_id, I2cOptions options); 1002 // ... 1003 }; 1004 1005 int kBusId; 1006 I2cOptions opts; 1007 1008 pw::sync::InlineBorrowable<ExampleI2c> i2c(std::in_place, kBusId, opts); 1009 1010 pw::Result<ConstByteSpan> ReadI2cData( 1011 pw::sync::Borrowable<pw::i2c::Initiator>& initiator, 1012 ByteSpan buffer); 1013 1014 pw::Result<ConstByteSpan> ReadData(ByteSpan buffer) { 1015 return ReadI2cData(i2c, buffer); 1016 } 1017 1018-------------------- 1019Signaling Primitives 1020-------------------- 1021Native signaling primitives tend to vary more compared to critial section locks 1022across different platforms. For example, although common signaling primtives 1023like semaphores are in most if not all RTOSes and even POSIX, it was not in the 1024STL before C++20. Likewise many C++ developers are surprised that conditional 1025variables tend to not be natively supported on RTOSes. Although you can usually 1026build any signaling primitive based on other native signaling primitives, this 1027may come with non-trivial added overhead in ROM, RAM, and execution efficiency. 1028 1029For this reason, Pigweed intends to provide some simpler signaling primitives 1030which exist to solve a narrow programming need but can be implemented as 1031efficiently as possible for the platform that it is used on. 1032 1033This simpler but highly portable class of signaling primitives is intended to 1034ensure that a portability efficiency tradeoff does not have to be made up front. 1035Today this is class of simpler signaling primitives is limited to the 1036:cpp:class:`pw::sync::ThreadNotification` and 1037:cpp:class:`pw::sync::TimedThreadNotification`. 1038 1039ThreadNotification 1040================== 1041.. cpp:namespace-push:: pw::sync 1042 1043The :cpp:class:`ThreadNotification` is a synchronization primitive that can be used to 1044permit a SINGLE thread to block and consume a latching, saturating 1045notification from multiple notifiers. 1046 1047.. Note:: 1048 Although only a single thread can block on a :cpp:class:`ThreadNotification` 1049 at a time, many instances may be used by a single thread just like binary 1050 semaphores. This is in contrast to some native RTOS APIs, such as direct 1051 task notifications, which re-use the same state within a thread's context. 1052 1053.. Warning:: 1054 This is a single consumer/waiter, multiple producer/notifier API! 1055 The acquire APIs must only be invoked by a single consuming thread. As a 1056 result, having multiple threads receiving notifications via the acquire API 1057 is unsupported. 1058 1059This is effectively a subset of the :cpp:class:`BinarySemaphore` API, except 1060that only a single thread can be notified and block at a time. 1061 1062The single consumer aspect of the API permits the use of a smaller and/or 1063faster native APIs such as direct thread signaling. This should be 1064backed by the most efficient native primitive for a target, regardless of 1065whether that is a semaphore, event flag group, condition variable, or something 1066else. 1067 1068The :cpp:class:`ThreadNotification` is initialized to being empty (latch is not 1069set). 1070 1071.. cpp:namespace-pop:: 1072 1073Generic BinarySemaphore-based Backend 1074------------------------------------- 1075This module provides a generic backend for 1076:cpp:class:`pw::sync::ThreadNotification` via 1077``pw_sync:binary_semaphore_thread_notification`` which uses a 1078:cpp:class:`pw::sync::BinarySemaphore` as the backing primitive. See 1079:ref:`BinarySemaphore <module-pw_sync-binary-semaphore>` for backend 1080availability. 1081 1082Optimized Backend 1083----------------- 1084.. list-table:: 1085 :header-rows: 1 1086 1087 * - Supported on 1088 - Optimized backend module 1089 * - FreeRTOS 1090 - ``pw_sync_freertos:thread_notification`` 1091 * - ThreadX 1092 - Not possible, use ``pw_sync:binary_semaphore_thread_notification`` 1093 * - embOS 1094 - Not needed, use ``pw_sync:binary_semaphore_thread_notification`` 1095 * - STL 1096 - Not planned, use ``pw_sync:binary_semaphore_thread_notification`` 1097 * - Baremetal 1098 - Planned 1099 * - Zephyr 1100 - Planned 1101 * - CMSIS-RTOS API v2 & RTX5 1102 - Planned 1103 1104C++ 1105--- 1106.. doxygenclass:: pw::sync::ThreadNotification 1107 :members: 1108 1109.. cpp:namespace-push:: pw::sync::ThreadNotification 1110 1111.. list-table:: 1112 :widths: 70 10 10 10 1113 :header-rows: 1 1114 1115 * - Safe to use in context 1116 - Thread 1117 - Interrupt 1118 - NMI 1119 * - :cpp:class:`pw::sync::ThreadNotification::ThreadNotification` 1120 - ✔ 1121 - 1122 - 1123 * - :cpp:func:`pw::sync::ThreadNotification::~ThreadNotification` 1124 - ✔ 1125 - 1126 - 1127 * - :cpp:func:`acquire` 1128 - ✔ 1129 - 1130 - 1131 * - :cpp:func:`try_acquire` 1132 - ✔ 1133 - 1134 - 1135 * - :cpp:func:`release` 1136 - ✔ 1137 - ✔ 1138 - 1139 1140.. cpp:namespace-pop:: 1141 1142 1143Examples in C++ 1144^^^^^^^^^^^^^^^ 1145.. code-block:: cpp 1146 1147 #include "pw_sync/thread_notification.h" 1148 #include "pw_thread/thread_core.h" 1149 1150 class FooHandler() : public pw::thread::ThreadCore { 1151 // Public API invoked by other threads and/or interrupts. 1152 void NewFooAvailable() { 1153 new_foo_notification_.release(); 1154 } 1155 1156 private: 1157 pw::sync::ThreadNotification new_foo_notification_; 1158 1159 // Thread function. 1160 void Run() override { 1161 while (true) { 1162 new_foo_notification_.acquire(); 1163 HandleFoo(); 1164 } 1165 } 1166 1167 void HandleFoo(); 1168 } 1169 1170TimedThreadNotification 1171======================= 1172The :cpp:class:`TimedThreadNotification` is an extension of the 1173:cpp:class:`ThreadNotification` which offers timeout and deadline based 1174semantics. 1175 1176The :cpp:class:`TimedThreadNotification` is initialized to being empty (latch is 1177not set). 1178 1179.. Warning:: 1180 This is a single consumer/waiter, multiple producer/notifier API! The 1181 acquire APIs must only be invoked by a single consuming thread. As a result, 1182 having multiple threads receiving notifications via the acquire API is 1183 unsupported. 1184 1185Generic BinarySemaphore-based Backend 1186------------------------------------- 1187This module provides a generic backend for 1188:cpp:class:`pw::sync::TimedThreadNotification` via 1189``pw_sync:binary_semaphore_timed_thread_notification`` which uses a 1190:cpp:class:`pw::sync::BinarySemaphore` as the backing primitive. See 1191:ref:`BinarySemaphore <module-pw_sync-binary-semaphore>` for backend 1192availability. 1193 1194Optimized Backend 1195----------------- 1196.. list-table:: 1197 :header-rows: 1 1198 1199 * - Supported on 1200 - Backend module 1201 * - FreeRTOS 1202 - ``pw_sync_freertos:timed_thread_notification`` 1203 * - ThreadX 1204 - Not possible, use ``pw_sync:binary_semaphore_timed_thread_notification`` 1205 * - embOS 1206 - Not needed, use ``pw_sync:binary_semaphore_timed_thread_notification`` 1207 * - STL 1208 - Not planned, use ``pw_sync:binary_semaphore_timed_thread_notification`` 1209 * - Zephyr 1210 - Planned 1211 * - CMSIS-RTOS API v2 & RTX5 1212 - Planned 1213 1214C++ 1215--- 1216.. doxygenclass:: pw::sync::TimedThreadNotification 1217 :members: 1218 1219.. cpp:namespace-push:: pw::sync::TimedThreadNotification 1220 1221.. list-table:: 1222 :widths: 70 10 10 10 1223 :header-rows: 1 1224 1225 * - Safe to use in context 1226 - Thread 1227 - Interrupt 1228 - NMI 1229 * - :cpp:class:`pw::sync::TimedThreadNotification::TimedThreadNotification` 1230 - ✔ 1231 - 1232 - 1233 * - :cpp:func:`pw::sync::TimedThreadNotification::~TimedThreadNotification` 1234 - ✔ 1235 - 1236 - 1237 * - :cpp:func:`acquire` 1238 - ✔ 1239 - 1240 - 1241 * - :cpp:func:`try_acquire` 1242 - ✔ 1243 - 1244 - 1245 * - :cpp:func:`try_acquire_for` 1246 - ✔ 1247 - 1248 - 1249 * - :cpp:func:`try_acquire_until` 1250 - ✔ 1251 - 1252 - 1253 * - :cpp:func:`release` 1254 - ✔ 1255 - ✔ 1256 - 1257 1258.. cpp:namespace-pop:: 1259 1260Examples in C++ 1261^^^^^^^^^^^^^^^ 1262.. code-block:: cpp 1263 1264 #include "pw_sync/timed_thread_notification.h" 1265 #include "pw_thread/thread_core.h" 1266 1267 class FooHandler() : public pw::thread::ThreadCore { 1268 // Public API invoked by other threads and/or interrupts. 1269 void NewFooAvailable() { 1270 new_foo_notification_.release(); 1271 } 1272 1273 private: 1274 pw::sync::TimedThreadNotification new_foo_notification_; 1275 1276 // Thread function. 1277 void Run() override { 1278 while (true) { 1279 if (new_foo_notification_.try_acquire_for(kNotificationTimeout)) { 1280 HandleFoo(); 1281 } 1282 DoOtherStuff(); 1283 } 1284 } 1285 1286 void HandleFoo(); 1287 void DoOtherStuff(); 1288 } 1289 1290CountingSemaphore 1291================= 1292.. cpp:namespace-push:: pw::sync 1293 1294The :cpp:class:`CountingSemaphore` is a synchronization primitive that can be 1295used for counting events and/or resource management where receiver(s) can block 1296on acquire until notifier(s) signal by invoking release. 1297 1298Note that unlike :cpp:class:`Mutex`, priority inheritance is not used by 1299semaphores meaning semaphores are subject to unbounded priority inversions. Due 1300to this, Pigweed does not recommend semaphores for mutual exclusion. 1301 1302The :cpp:class:`CountingSemaphore` is initialized to being empty or having no 1303tokens. 1304 1305The entire API is thread safe, but only a subset is interrupt safe. 1306 1307.. Note:: 1308 If there is only a single consuming thread, we recommend using a 1309 :cpp:class:`ThreadNotification` instead which can be much more efficient on 1310 some RTOSes such as FreeRTOS. 1311 1312.. cpp:namespace-pop:: 1313 1314.. Warning:: 1315 Releasing multiple tokens is often not natively supported, meaning you may 1316 end up invoking the native kernel API many times, i.e. once per token you 1317 are releasing! 1318 1319.. list-table:: 1320 :header-rows: 1 1321 1322 * - Supported on 1323 - Backend module 1324 * - FreeRTOS 1325 - :ref:`module-pw_sync_freertos` 1326 * - ThreadX 1327 - :ref:`module-pw_sync_threadx` 1328 * - embOS 1329 - :ref:`module-pw_sync_embos` 1330 * - STL 1331 - :ref:`module-pw_sync_stl` 1332 * - Zephyr 1333 - Planned 1334 * - CMSIS-RTOS API v2 & RTX5 1335 - Planned 1336 1337C++ 1338--- 1339.. doxygenclass:: pw::sync::CountingSemaphore 1340 :members: 1341 1342.. cpp:namespace-push:: pw::sync::CountingSemaphore 1343 1344.. list-table:: 1345 :widths: 70 10 10 10 1346 :header-rows: 1 1347 1348 * - Safe to use in context 1349 - Thread 1350 - Interrupt 1351 - NMI 1352 * - :cpp:class:`pw::sync::CountingSemaphore::CountingSemaphore` 1353 - ✔ 1354 - 1355 - 1356 * - :cpp:func:`pw::sync::CountingSemaphore::~CountingSemaphore` 1357 - ✔ 1358 - 1359 - 1360 * - :cpp:func:`acquire` 1361 - ✔ 1362 - 1363 - 1364 * - :cpp:func:`try_acquire` 1365 - ✔ 1366 - ✔ 1367 - 1368 * - :cpp:func:`try_acquire_for` 1369 - ✔ 1370 - 1371 - 1372 * - :cpp:func:`try_acquire_until` 1373 - ✔ 1374 - 1375 - 1376 * - :cpp:func:`release` 1377 - ✔ 1378 - ✔ 1379 - 1380 * - :cpp:func:`max` 1381 - ✔ 1382 - ✔ 1383 - ✔ 1384 1385.. cpp:namespace-pop:: 1386 1387Examples in C++ 1388^^^^^^^^^^^^^^^ 1389As an example, a counting sempahore can be useful to run periodic tasks at 1390frequencies near or higher than the system clock tick rate in a way which lets 1391you detect whether you ever fall behind. 1392 1393.. code-block:: cpp 1394 1395 #include "pw_sync/counting_semaphore.h" 1396 #include "pw_thread/thread_core.h" 1397 1398 class PeriodicWorker() : public pw::thread::ThreadCore { 1399 // Public API invoked by a higher frequency timer interrupt. 1400 void TimeToExecute() { 1401 periodic_run_semaphore_.release(); 1402 } 1403 1404 private: 1405 pw::sync::CountingSemaphore periodic_run_semaphore_; 1406 1407 // Thread function. 1408 void Run() override { 1409 while (true) { 1410 size_t behind_by_n_cycles = 0; 1411 periodic_run_semaphore_.acquire(); // Wait to run until it's time. 1412 while (periodic_run_semaphore_.try_acquire()) { 1413 ++behind_by_n_cycles; 1414 } 1415 if (behind_by_n_cycles > 0) { 1416 PW_LOG_WARNING("Not keeping up, behind by %d cycles", 1417 behind_by_n_cycles); 1418 } 1419 DoPeriodicWork(); 1420 } 1421 } 1422 1423 void DoPeriodicWork(); 1424 } 1425 1426.. _module-pw_sync-binary-semaphore: 1427 1428BinarySemaphore 1429=============== 1430.. cpp:namespace-push:: pw::sync 1431 1432:cpp:class:`BinarySemaphore` is a specialization of CountingSemaphore with an 1433arbitrary token limit of 1. Note that that ``max()`` is >= 1, meaning it may be 1434released up to ``max()`` times but only acquired once for those N releases. 1435 1436Implementations of :cpp:class:`BinarySemaphore` are typically more 1437efficient than the default implementation of :cpp:class:`CountingSemaphore`. 1438 1439The :cpp:class:`BinarySemaphore` is initialized to being empty or having no 1440tokens. 1441 1442.. cpp:namespace-pop:: 1443 1444The entire API is thread safe, but only a subset is interrupt safe. 1445 1446.. Note:: 1447 If there is only a single consuming thread, we recommend using a 1448 ThreadNotification instead which can be much more efficient on some RTOSes 1449 such as FreeRTOS. 1450 1451.. list-table:: 1452 :header-rows: 1 1453 1454 * - Supported on 1455 - Backend module 1456 * - FreeRTOS 1457 - :ref:`module-pw_sync_freertos` 1458 * - ThreadX 1459 - :ref:`module-pw_sync_threadx` 1460 * - embOS 1461 - :ref:`module-pw_sync_embos` 1462 * - STL 1463 - :ref:`module-pw_sync_stl` 1464 * - Zephyr 1465 - Planned 1466 * - CMSIS-RTOS API v2 & RTX5 1467 - Planned 1468 1469C++ 1470--- 1471.. doxygenclass:: pw::sync::BinarySemaphore 1472 :members: 1473 1474.. cpp:namespace-push:: pw::sync::BinarySemaphore 1475 1476.. list-table:: 1477 :widths: 70 10 10 10 1478 :header-rows: 1 1479 1480 * - Safe to use in context 1481 - Thread 1482 - Interrupt 1483 - NMI 1484 * - :cpp:class:`pw::sync::BinarySemaphore::BinarySemaphore` 1485 - ✔ 1486 - 1487 - 1488 * - :cpp:func:`pw::sync::BinarySemaphore::~BinarySemaphore` 1489 - ✔ 1490 - 1491 - 1492 * - :cpp:func:`acquire` 1493 - ✔ 1494 - 1495 - 1496 * - :cpp:func:`try_acquire` 1497 - ✔ 1498 - ✔ 1499 - 1500 * - :cpp:func:`try_acquire_for` 1501 - ✔ 1502 - 1503 - 1504 * - :cpp:func:`try_acquire_until` 1505 - ✔ 1506 - 1507 - 1508 * - :cpp:func:`release` 1509 - ✔ 1510 - ✔ 1511 - 1512 * - :cpp:func:`max` 1513 - ✔ 1514 - ✔ 1515 - ✔ 1516 1517.. cpp:namespace-pop:: 1518 1519Examples in C++ 1520^^^^^^^^^^^^^^^ 1521.. code-block:: cpp 1522 1523 #include "pw_sync/binary_semaphore.h" 1524 #include "pw_thread/thread_core.h" 1525 1526 class FooHandler() : public pw::thread::ThreadCore { 1527 // Public API invoked by other threads and/or interrupts. 1528 void NewFooAvailable() { 1529 new_foo_semaphore_.release(); 1530 } 1531 1532 private: 1533 pw::sync::BinarySemaphore new_foo_semaphore_; 1534 1535 // Thread function. 1536 void Run() override { 1537 while (true) { 1538 if (new_foo_semaphore_.try_acquire_for(kNotificationTimeout)) { 1539 HandleFoo(); 1540 } 1541 DoOtherStuff(); 1542 } 1543 } 1544 1545 void HandleFoo(); 1546 void DoOtherStuff(); 1547 } 1548 1549Conditional Variables 1550===================== 1551:cpp:class:`pw::sync::ConditionVariable` provides a condition variable 1552implementation that provides semantics and an API very similar to 1553`std::condition_variable 1554<https://en.cppreference.com/w/cpp/thread/condition_variable>`_ in the C++ 1555Standard Library. 1556