blob: aec5900e60adf1c7eae861fbc5114694814f472d [file] [log] [blame]
Ewout van Bekkum58901932020-11-09 12:46:52 -08001.. _module-pw_sync:
2
Ewout van Bekkumf84638b2021-03-12 16:09:08 -08003=======
Ewout van Bekkum58901932020-11-09 12:46:52 -08004pw_sync
Ewout van Bekkumf84638b2021-03-12 16:09:08 -08005=======
6The ``pw_sync`` module contains utilities for synchronizing between threads
7and/or interrupts through signaling primitives and critical section lock
8primitives.
Ewout van Bekkum58901932020-11-09 12:46:52 -08009
Ewout van Bekkumf84638b2021-03-12 16:09:08 -080010.. contents::
11 :local:
12 :depth: 2
13
14.. Warning::
15 This module is still under construction, the API is not yet stable.
16
17.. Note::
18 The objects in this module do not have an Init() style public API which is
19 common in many RTOS C APIs. Instead, they rely on being able to invoke the
20 native initialization APIs for synchronization primitives during C++
21 construction.
22 In order to support global statically constructed synchronization without
23 constexpr constructors, the user and/or backend **MUST** ensure that any
24 initialization required in your environment is done prior to the creation
25 and/or initialization of the native synchronization primitives
26 (e.g. kernel initialization).
27
28--------------------------------
29Critical Section Lock Primitives
30--------------------------------
31The critical section lock primitives provided by this module comply with
32`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_,
33`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and where
34relevant
35`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_ C++
36named requirements. This means that they are compatible with existing helpers in
37the STL's ``<mutex>`` thread support library. For example `std::lock_guard <https://en.cppreference.com/w/cpp/thread/lock_guard>`_
38and `std::unique_lock <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::timed_mutex <https://en.cppreference.com/w/cpp/thread/timed_mutex>`_ like,
49meaning it is a
50`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_,
51`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and
52`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_.
53
Ewout van Bekkum3b9eca42021-04-02 14:54:02 -070054.. Warning::
55 This interface will likely be split between a Mutex and TimedMutex to ease
56 portability for baremetal support.
57
58.. list-table::
59
60 * - *Supported on*
61 - *Backend module*
62 * - FreeRTOS
63 - :ref:`module-pw_sync_freertos`
64 * - ThreadX
65 - :ref:`module-pw_sync_threadx`
66 * - embOS
67 - :ref:`module-pw_sync_embos`
68 * - STL
69 - :ref:`module-pw_sync_stl`
70 * - Baremetal
71 - Planned
72 * - Zephyr
73 - Planned
74 * - CMSIS-RTOS API v2 & RTX5
75 - Planned
Ewout van Bekkumf84638b2021-03-12 16:09:08 -080076
77C++
78---
79.. cpp:class:: pw::sync::Mutex
80
81 .. cpp:function:: void lock()
82
83 Locks the mutex, blocking indefinitely. Failures are fatal.
84
85 **Precondition:** The lock isn't already held by this thread. Recursive
86 locking is undefined behavior.
87
88 .. cpp:function:: bool try_lock()
89
90 Attempts to lock the mutex in a non-blocking manner.
91 Returns true if the mutex was successfully acquired.
92
93 **Precondition:** The lock isn't already held by this thread. Recursive
94 locking is undefined behavior.
95
96 .. cpp:function:: bool try_lock_for(chrono::SystemClock::duration for_at_least)
97
98 Attempts to lock the mutex where, if needed, blocking for at least the
99 specified duration.
100 Returns true if the mutex was successfully acquired.
101
102 **Precondition:** The lock isn't already held by this thread. Recursive
103 locking is undefined behavior.
104
105 .. cpp:function:: bool try_lock_until(chrono::SystemClock::time_point until_at_least)
106
107 Attempts to lock the mutex where, if needed, blocking until at least the
108 specified time_point.
109 Returns true if the mutex was successfully acquired.
110
111 **Precondition:** The lock isn't already held by this thread. Recursive
112 locking is undefined behavior.
113
114 .. cpp:function:: void unlock()
115
116 Unlocks the mutex. Failures are fatal.
117
118 **Precondition:** The mutex is held by this thread.
119
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800120
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700121 .. list-table::
122
123 * - *Safe to use in context*
124 - *Thread*
125 - *Interrupt*
126 - *NMI*
127 * - ``Mutex::Mutex``
128 - ✔
129 -
130 -
131 * - ``Mutex::~Mutex``
132 - ✔
133 -
134 -
135 * - ``void Mutex::lock``
136 - ✔
137 -
138 -
139 * - ``bool Mutex::try_lock``
140 - ✔
141 -
142 -
143 * - ``bool Mutex::try_lock_for``
144 - ✔
145 -
146 -
147 * - ``bool Mutex::try_lock_until``
148 - ✔
149 -
150 -
151 * - ``void Mutex::unlock``
152 - ✔
153 -
154 -
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800155
156Examples in C++
157^^^^^^^^^^^^^^^
158.. code-block:: cpp
159
160 #include "pw_chrono/system_clock.h"
161 #include "pw_sync/mutex.h"
162
163 pw::sync::Mutex mutex;
164
165 void ThreadSafeCriticalSection() {
166 mutex.lock();
167 NotThreadSafeCriticalSection();
168 mutex.unlock();
169 }
170
171 bool ThreadSafeCriticalSectionWithTimeout(
172 const SystemClock::duration timeout) {
173 if (!mutex.try_lock_for(timeout)) {
174 return false;
175 }
176 NotThreadSafeCriticalSection();
177 mutex.unlock();
178 return true;
179 }
180
181
182Alternatively you can use C++'s RAII helpers to ensure you always unlock.
183
184.. code-block:: cpp
185
186 #include <mutex>
187
188 #include "pw_chrono/system_clock.h"
189 #include "pw_sync/mutex.h"
190
191 pw::sync::Mutex mutex;
192
193 void ThreadSafeCriticalSection() {
194 std::lock_guard lock(mutex);
195 NotThreadSafeCriticalSection();
196 }
197
198 bool ThreadSafeCriticalSectionWithTimeout(
199 const SystemClock::duration timeout) {
200 std::unique_lock lock(mutex, std::defer_lock);
201 if (!lock.try_lock_for(timeout)) {
202 return false;
203 }
204 NotThreadSafeCriticalSection();
205 return true;
206 }
207
208
209
210C
211-
212The Mutex must be created in C++, however it can be passed into C using the
213``pw_sync_Mutex`` opaque struct alias.
214
215.. cpp:function:: void pw_sync_Mutex_Lock(pw_sync_Mutex* mutex)
216
217 Invokes the ``Mutex::lock`` member function on the given ``mutex``.
218
219.. cpp:function:: bool pw_sync_Mutex_TryLock(pw_sync_Mutex* mutex)
220
221 Invokes the ``Mutex::try_lock`` member function on the given ``mutex``.
222
223.. cpp:function:: bool pw_sync_Mutex_TryLockFor(pw_sync_Mutex* mutex, pw_chrono_SystemClock_Duration for_at_least)
224
225 Invokes the ``Mutex::try_lock_for`` member function on the given ``mutex``.
226
227.. cpp:function:: bool pw_sync_Mutex_TryLockUntil(pw_sync_Mutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least)
228
229 Invokes the ``Mutex::try_lock_until`` member function on the given ``mutex``.
230
231.. cpp:function:: void pw_sync_Mutex_Unlock(pw_sync_Mutex* mutex)
232
233 Invokes the ``Mutex::unlock`` member function on the given ``mutex``.
234
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700235.. list-table::
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800236
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700237 * - *Safe to use in context*
238 - *Thread*
239 - *Interrupt*
240 - *NMI*
241 * - ``void pw_sync_Mutex_Lock``
242 - ✔
243 -
244 -
245 * - ``bool pw_sync_Mutex_TryLock``
246 - ✔
247 -
248 -
249 * - ``bool pw_sync_Mutex_TryLockFor``
250 - ✔
251 -
252 -
253 * - ``bool pw_sync_Mutex_TryLockUntil``
254 - ✔
255 -
256 -
257 * - ``void pw_sync_Mutex_Unlock``
258 - ✔
259 -
260 -
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800261
262Example in C
263^^^^^^^^^^^^
264.. code-block:: cpp
265
266 #include "pw_chrono/system_clock.h"
267 #include "pw_sync/mutex.h"
268
269 pw::sync::Mutex mutex;
270
271 extern pw_sync_Mutex mutex; // This can only be created in C++.
272
273 void ThreadSafeCriticalSection(void) {
274 pw_sync_Mutex_Lock(&mutex);
275 NotThreadSafeCriticalSection();
276 pw_sync_Mutex_Unlock(&mutex);
277 }
278
279 bool ThreadSafeCriticalSectionWithTimeout(
280 const pw_chrono_SystemClock_Duration timeout) {
281 if (!pw_sync_Mutex_TryLockFor(&mutex, timeout)) {
282 return false;
283 }
284 NotThreadSafeCriticalSection();
285 pw_sync_Mutex_Unlock(&mutex);
286 return true;
287 }
288
289
290InterruptSpinLock
291=================
292The InterruptSpinLock is a synchronization primitive that can be used to protect
293shared data from being simultaneously accessed by multiple threads and/or
294interrupts as a targeted global lock, with the exception of Non-Maskable
295Interrupts (NMIs). It offers exclusive, non-recursive ownership semantics where
296IRQs up to a backend defined level of "NMIs" will be masked to solve
297priority-inversion.
298
299This InterruptSpinLock relies on built-in local interrupt masking to make it
300interrupt safe without requiring the caller to separately mask and unmask
301interrupts when using this primitive.
302
303Unlike global interrupt locks, this also works safely and efficiently on SMP
304systems. On systems which are not SMP, spinning is not required but some state
305may still be used to detect recursion.
306
307The InterruptSpinLock is a
308`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_
309and
310`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_.
311
Ewout van Bekkum3b9eca42021-04-02 14:54:02 -0700312.. list-table::
313
314 * - *Supported on*
315 - *Backend module*
316 * - FreeRTOS
317 - :ref:`module-pw_sync_freertos`
318 * - ThreadX
319 - :ref:`module-pw_sync_threadx`
320 * - embOS
321 - :ref:`module-pw_sync_embos`
322 * - STL
323 - :ref:`module-pw_sync_stl`
324 * - Baremetal
325 - Planned, not ready for use
326 * - Zephyr
327 - Planned
328 * - CMSIS-RTOS API v2 & RTX5
329 - Planned
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800330
331C++
332---
333.. cpp:class:: pw::sync::InterruptSpinLock
334
335 .. cpp:function:: void lock()
336
337 Locks the spinlock, blocking indefinitely. Failures are fatal.
338
339 **Precondition:** Recursive locking is undefined behavior.
340
341 .. cpp:function:: bool try_lock()
342
343 Attempts to lock the spinlock in a non-blocking manner.
344 Returns true if the spinlock was successfully acquired.
345
346 **Precondition:** Recursive locking is undefined behavior.
347
348 .. cpp:function:: void unlock()
349
350 Unlocks the mutex. Failures are fatal.
351
352 **Precondition:** The spinlock is held by the caller.
353
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700354 .. list-table::
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800355
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700356 * - *Safe to use in context*
357 - *Thread*
358 - *Interrupt*
359 - *NMI*
360 * - ``InterruptSpinLock::InterruptSpinLock``
361 - ✔
362 - ✔
363 -
364 * - ``InterruptSpinLock::~InterruptSpinLock``
365 - ✔
366 - ✔
367 -
368 * - ``void InterruptSpinLock::lock``
369 - ✔
370 - ✔
371 -
372 * - ``bool InterruptSpinLock::try_lock``
373 - ✔
374 - ✔
375 -
376 * - ``void InterruptSpinLock::unlock``
377 - ✔
378 - ✔
379 -
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800380
381Examples in C++
382^^^^^^^^^^^^^^^
383.. code-block:: cpp
384
385 #include "pw_sync/interrupt_spin_lock.h"
386
387 pw::sync::InterruptSpinLock interrupt_spin_lock;
388
389 void InterruptSafeCriticalSection() {
390 interrupt_spin_lock.lock();
391 NotThreadSafeCriticalSection();
392 interrupt_spin_lock.unlock();
393 }
394
395
396Alternatively you can use C++'s RAII helpers to ensure you always unlock.
397
398.. code-block:: cpp
399
400 #include <mutex>
401
402 #include "pw_sync/interrupt_spin_lock.h"
403
404 pw::sync::InterruptSpinLock interrupt_spin_lock;
405
406 void InterruptSafeCriticalSection() {
407 std::lock_guard lock(interrupt_spin_lock);
408 NotThreadSafeCriticalSection();
409 }
410
411
412C
413-
414The InterruptSpinLock must be created in C++, however it can be passed into C using the
415``pw_sync_InterruptSpinLock`` opaque struct alias.
416
417.. cpp:function:: void pw_sync_InterruptSpinLock_Lock(pw_sync_InterruptSpinLock* interrupt_spin_lock)
418
419 Invokes the ``InterruptSpinLock::lock`` member function on the given ``interrupt_spin_lock``.
420
421.. cpp:function:: bool pw_sync_InterruptSpinLock_TryLock(pw_sync_InterruptSpinLock* interrupt_spin_lock)
422
423 Invokes the ``InterruptSpinLock::try_lock`` member function on the given ``interrupt_spin_lock``.
424
425.. cpp:function:: void pw_sync_InterruptSpinLock_Unlock(pw_sync_InterruptSpinLock* interrupt_spin_lock)
426
427 Invokes the ``InterruptSpinLock::unlock`` member function on the given ``interrupt_spin_lock``.
428
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700429.. list-table::
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800430
Ewout van Bekkumfe700662021-04-02 16:48:50 -0700431 * - *Safe to use in context*
432 - *Thread*
433 - *Interrupt*
434 - *NMI*
435 * - ``void pw_sync_InterruptSpinLock_Lock``
436 - ✔
437 - ✔
438 -
439 * - ``bool pw_sync_InterruptSpinLock_TryLock``
440 - ✔
441 - ✔
442 -
443 * - ``void pw_sync_InterruptSpinLock_Unlock``
444 - ✔
445 - ✔
446 -
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800447
448Example in C
449^^^^^^^^^^^^
450.. code-block:: cpp
451
452 #include "pw_chrono/system_clock.h"
453 #include "pw_sync/interrupt_spin_lock.h"
454
455 pw::sync::InterruptSpinLock interrupt_spin_lock;
456
457 extern pw_sync_InterruptSpinLock interrupt_spin_lock; // This can only be created in C++.
458
459 void InterruptSafeCriticalSection(void) {
460 pw_sync_InterruptSpinLock_Lock(&interrupt_spin_lock);
461 NotThreadSafeCriticalSection();
462 pw_sync_InterruptSpinLock_Unlock(&interrupt_spin_lock);
463 }
464
465
466--------------------
467Signaling Primitives
468--------------------
469
Ewout van Bekkum3b9eca42021-04-02 14:54:02 -0700470Native signaling primitives tend to vary more compared to critial section locks
471across different platforms. For example, although common signaling primtives
472like semaphores are in most if not all RTOSes and even POSIX, it was not in the
473STL before C++20. Likewise many C++ developers are surprised that conditional
474variables tend to not be natively supported on RTOSes. Although you can usually
475build any signaling primitive based on other native signaling primitives, this
476may come with non-trivial added overhead in ROM, RAM, and execution efficiency.
477
478For this reason, Pigweed intends to provide some "simpler" signaling primitives
479which exist to solve a narrow programming need but can be implemented as
480efficiently as possible for the platform that it is used on.
481
482This simpler but highly portable class of signaling primitives is intended to
483ensure that a portability efficiency tradeoff does not have to be made up front.
484For example we intend to provide a ``pw::sync::Notification`` facade which
485permits a singler consumer to block until an event occurs. This should be
486backed by the most efficient native primitive for a target, regardless of
487whether that is a semaphore, event flag group, condition variable, or something
488else.
489
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800490CountingSemaphore
491=================
492The CountingSemaphore is a synchronization primitive that can be used for
493counting events and/or resource management where receiver(s) can block on
494acquire until notifier(s) signal by invoking release.
495
496Note that unlike Mutexes, priority inheritance is not used by semaphores meaning
497semaphores are subject to unbounded priority inversions. Due to this, Pigweed
498does not recommend semaphores for mutual exclusion.
499
500The CountingSemaphore is initialized to being empty or having no tokens.
501
502The entire API is thread safe, but only a subset is interrupt safe. None of it
503is NMI safe.
504
505.. Warning::
506 Releasing multiple tokens is often not natively supported, meaning you may
507 end up invoking the native kernel API many times, i.e. once per token you
508 are releasing!
509
Ewout van Bekkum3b9eca42021-04-02 14:54:02 -0700510.. list-table::
511
512 * - *Supported on*
513 - *Backend module*
514 * - FreeRTOS
515 - :ref:`module-pw_sync_freertos`
516 * - ThreadX
517 - :ref:`module-pw_sync_threadx`
518 * - embOS
519 - :ref:`module-pw_sync_embos`
520 * - STL
521 - :ref:`module-pw_sync_stl`
522 * - Zephyr
523 - Planned
524 * - CMSIS-RTOS API v2 & RTX5
525 - Planned
526
Ewout van Bekkumf84638b2021-03-12 16:09:08 -0800527BinarySemaphore
528===============
529BinarySemaphore is a specialization of CountingSemaphore with an arbitrary token
530limit of 1. Note that that ``max()`` is >= 1, meaning it may be released up to
531``max()`` times but only acquired once for those N releases.
532
533Implementations of BinarySemaphore are typically more efficient than the
534default implementation of CountingSemaphore.
535
536The BinarySemaphore is initialized to being empty or having no tokens.
537
538The entire API is thread safe, but only a subset is interrupt safe. None of it
539is NMI safe.
Ewout van Bekkum3b9eca42021-04-02 14:54:02 -0700540
541.. list-table::
542
543 * - *Supported on*
544 - *Backend module*
545 * - FreeRTOS
546 - :ref:`module-pw_sync_freertos`
547 * - ThreadX
548 - :ref:`module-pw_sync_threadx`
549 * - embOS
550 - :ref:`module-pw_sync_embos`
551 * - STL
552 - :ref:`module-pw_sync_stl`
553 * - Zephyr
554 - Planned
555 * - CMSIS-RTOS API v2 & RTX5
556 - Planned
557
558Coming Soon
559===========
560We are intending to provide facades for:
561
562* ``pw::sync::Notification``: A portable abstraction to allow threads to receive
563 notification of a single occurrence of a single event.
564
565* ``pw::sync::EventGroup`` A facade for a common primitive on RTOSes like
566 FreeRTOS, RTX5, ThreadX, and embOS which permit threads and interrupts to
567 signal up to 32 events. This permits others threads to be notified when either
568 any or some combination of these events have been signaled. This is frequently
569 used as an alternative to a set of binary semaphore(s). This is not supported
570 natively on Zephyr.