blob: 8af6fea8e62d891e13958bbb3d5dbab58ac715ae [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
54
55C++
56---
57.. cpp:class:: pw::sync::Mutex
58
59 .. cpp:function:: void lock()
60
61 Locks the mutex, blocking indefinitely. Failures are fatal.
62
63 **Precondition:** The lock isn't already held by this thread. Recursive
64 locking is undefined behavior.
65
66 .. cpp:function:: bool try_lock()
67
68 Attempts to lock the mutex in a non-blocking manner.
69 Returns true if the mutex was successfully acquired.
70
71 **Precondition:** The lock isn't already held by this thread. Recursive
72 locking is undefined behavior.
73
74 .. cpp:function:: bool try_lock_for(chrono::SystemClock::duration for_at_least)
75
76 Attempts to lock the mutex where, if needed, blocking for at least the
77 specified duration.
78 Returns true if the mutex was successfully acquired.
79
80 **Precondition:** The lock isn't already held by this thread. Recursive
81 locking is undefined behavior.
82
83 .. cpp:function:: bool try_lock_until(chrono::SystemClock::time_point until_at_least)
84
85 Attempts to lock the mutex where, if needed, blocking until at least the
86 specified time_point.
87 Returns true if the mutex was successfully acquired.
88
89 **Precondition:** The lock isn't already held by this thread. Recursive
90 locking is undefined behavior.
91
92 .. cpp:function:: void unlock()
93
94 Unlocks the mutex. Failures are fatal.
95
96 **Precondition:** The mutex is held by this thread.
97
98 +--------------------------------+----------+-------------+-------+
99 | *Safe to use in context* | *Thread* | *Interrupt* | *NMI* |
100 +--------------------------------+----------+-------------+-------+
101 | ``Mutex::Mutex`` | ✔ | | |
102 +--------------------------------+----------+-------------+-------+
103 | ``Mutex::~Mutex`` | ✔ | | |
104 +--------------------------------+----------+-------------+-------+
105 | ``void Mutex::lock`` | ✔ | | |
106 +--------------------------------+----------+-------------+-------+
107 | ``bool Mutex::try_lock`` | ✔ | | |
108 +--------------------------------+----------+-------------+-------+
109 | ``bool Mutex::try_lock_for`` | ✔ | | |
110 +--------------------------------+----------+-------------+-------+
111 | ``bool Mutex::try_lock_until`` | ✔ | | |
112 +--------------------------------+----------+-------------+-------+
113 | ``void Mutex::unlock`` | ✔ | | |
114 +--------------------------------+----------+-------------+-------+
115
116
117Examples in C++
118^^^^^^^^^^^^^^^
119.. code-block:: cpp
120
121 #include "pw_chrono/system_clock.h"
122 #include "pw_sync/mutex.h"
123
124 pw::sync::Mutex mutex;
125
126 void ThreadSafeCriticalSection() {
127 mutex.lock();
128 NotThreadSafeCriticalSection();
129 mutex.unlock();
130 }
131
132 bool ThreadSafeCriticalSectionWithTimeout(
133 const SystemClock::duration timeout) {
134 if (!mutex.try_lock_for(timeout)) {
135 return false;
136 }
137 NotThreadSafeCriticalSection();
138 mutex.unlock();
139 return true;
140 }
141
142
143Alternatively you can use C++'s RAII helpers to ensure you always unlock.
144
145.. code-block:: cpp
146
147 #include <mutex>
148
149 #include "pw_chrono/system_clock.h"
150 #include "pw_sync/mutex.h"
151
152 pw::sync::Mutex mutex;
153
154 void ThreadSafeCriticalSection() {
155 std::lock_guard lock(mutex);
156 NotThreadSafeCriticalSection();
157 }
158
159 bool ThreadSafeCriticalSectionWithTimeout(
160 const SystemClock::duration timeout) {
161 std::unique_lock lock(mutex, std::defer_lock);
162 if (!lock.try_lock_for(timeout)) {
163 return false;
164 }
165 NotThreadSafeCriticalSection();
166 return true;
167 }
168
169
170
171C
172-
173The Mutex must be created in C++, however it can be passed into C using the
174``pw_sync_Mutex`` opaque struct alias.
175
176.. cpp:function:: void pw_sync_Mutex_Lock(pw_sync_Mutex* mutex)
177
178 Invokes the ``Mutex::lock`` member function on the given ``mutex``.
179
180.. cpp:function:: bool pw_sync_Mutex_TryLock(pw_sync_Mutex* mutex)
181
182 Invokes the ``Mutex::try_lock`` member function on the given ``mutex``.
183
184.. cpp:function:: bool pw_sync_Mutex_TryLockFor(pw_sync_Mutex* mutex, pw_chrono_SystemClock_Duration for_at_least)
185
186 Invokes the ``Mutex::try_lock_for`` member function on the given ``mutex``.
187
188.. cpp:function:: bool pw_sync_Mutex_TryLockUntil(pw_sync_Mutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least)
189
190 Invokes the ``Mutex::try_lock_until`` member function on the given ``mutex``.
191
192.. cpp:function:: void pw_sync_Mutex_Unlock(pw_sync_Mutex* mutex)
193
194 Invokes the ``Mutex::unlock`` member function on the given ``mutex``.
195
196+-------------------------------------+----------+-------------+-------+
197| *Safe to use in context* | *Thread* | *Interrupt* | *NMI* |
198+-------------------------------------+----------+-------------+-------+
199| ``void pw_sync_Mutex_Lock`` | ✔ | | |
200+-------------------------------------+----------+-------------+-------+
201| ``bool pw_sync_Mutex_TryLock`` | ✔ | | |
202+-------------------------------------+----------+-------------+-------+
203| ``bool pw_sync_Mutex_TryLockFor`` | ✔ | | |
204+-------------------------------------+----------+-------------+-------+
205| ``bool pw_sync_Mutex_TryLockUntil`` | ✔ | | |
206+-------------------------------------+----------+-------------+-------+
207| ``void pw_sync_Mutex_Unlock`` | ✔ | | |
208+-------------------------------------+----------+-------------+-------+
209
210
211Example in C
212^^^^^^^^^^^^
213.. code-block:: cpp
214
215 #include "pw_chrono/system_clock.h"
216 #include "pw_sync/mutex.h"
217
218 pw::sync::Mutex mutex;
219
220 extern pw_sync_Mutex mutex; // This can only be created in C++.
221
222 void ThreadSafeCriticalSection(void) {
223 pw_sync_Mutex_Lock(&mutex);
224 NotThreadSafeCriticalSection();
225 pw_sync_Mutex_Unlock(&mutex);
226 }
227
228 bool ThreadSafeCriticalSectionWithTimeout(
229 const pw_chrono_SystemClock_Duration timeout) {
230 if (!pw_sync_Mutex_TryLockFor(&mutex, timeout)) {
231 return false;
232 }
233 NotThreadSafeCriticalSection();
234 pw_sync_Mutex_Unlock(&mutex);
235 return true;
236 }
237
238
239InterruptSpinLock
240=================
241The InterruptSpinLock is a synchronization primitive that can be used to protect
242shared data from being simultaneously accessed by multiple threads and/or
243interrupts as a targeted global lock, with the exception of Non-Maskable
244Interrupts (NMIs). It offers exclusive, non-recursive ownership semantics where
245IRQs up to a backend defined level of "NMIs" will be masked to solve
246priority-inversion.
247
248This InterruptSpinLock relies on built-in local interrupt masking to make it
249interrupt safe without requiring the caller to separately mask and unmask
250interrupts when using this primitive.
251
252Unlike global interrupt locks, this also works safely and efficiently on SMP
253systems. On systems which are not SMP, spinning is not required but some state
254may still be used to detect recursion.
255
256The InterruptSpinLock is a
257`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_
258and
259`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_.
260
261
262C++
263---
264.. cpp:class:: pw::sync::InterruptSpinLock
265
266 .. cpp:function:: void lock()
267
268 Locks the spinlock, blocking indefinitely. Failures are fatal.
269
270 **Precondition:** Recursive locking is undefined behavior.
271
272 .. cpp:function:: bool try_lock()
273
274 Attempts to lock the spinlock in a non-blocking manner.
275 Returns true if the spinlock was successfully acquired.
276
277 **Precondition:** Recursive locking is undefined behavior.
278
279 .. cpp:function:: void unlock()
280
281 Unlocks the mutex. Failures are fatal.
282
283 **Precondition:** The spinlock is held by the caller.
284
285 +-------------------------------------------+----------+-------------+-------+
286 | *Safe to use in context* | *Thread* | *Interrupt* | *NMI* |
287 +-------------------------------------------+----------+-------------+-------+
288 | ``InterruptSpinLock::InterruptSpinLock`` | ✔ | ✔ | |
289 +-------------------------------------------+----------+-------------+-------+
290 | ``InterruptSpinLock::~InterruptSpinLock`` | ✔ | ✔ | |
291 +-------------------------------------------+----------+-------------+-------+
292 | ``void InterruptSpinLock::lock`` | ✔ | ✔ | |
293 +-------------------------------------------+----------+-------------+-------+
294 | ``bool InterruptSpinLock::try_lock`` | ✔ | ✔ | |
295 +-------------------------------------------+----------+-------------+-------+
296 | ``void InterruptSpinLock::unlock`` | ✔ | ✔ | |
297 +-------------------------------------------+----------+-------------+-------+
298
299
300Examples in C++
301^^^^^^^^^^^^^^^
302.. code-block:: cpp
303
304 #include "pw_sync/interrupt_spin_lock.h"
305
306 pw::sync::InterruptSpinLock interrupt_spin_lock;
307
308 void InterruptSafeCriticalSection() {
309 interrupt_spin_lock.lock();
310 NotThreadSafeCriticalSection();
311 interrupt_spin_lock.unlock();
312 }
313
314
315Alternatively you can use C++'s RAII helpers to ensure you always unlock.
316
317.. code-block:: cpp
318
319 #include <mutex>
320
321 #include "pw_sync/interrupt_spin_lock.h"
322
323 pw::sync::InterruptSpinLock interrupt_spin_lock;
324
325 void InterruptSafeCriticalSection() {
326 std::lock_guard lock(interrupt_spin_lock);
327 NotThreadSafeCriticalSection();
328 }
329
330
331C
332-
333The InterruptSpinLock must be created in C++, however it can be passed into C using the
334``pw_sync_InterruptSpinLock`` opaque struct alias.
335
336.. cpp:function:: void pw_sync_InterruptSpinLock_Lock(pw_sync_InterruptSpinLock* interrupt_spin_lock)
337
338 Invokes the ``InterruptSpinLock::lock`` member function on the given ``interrupt_spin_lock``.
339
340.. cpp:function:: bool pw_sync_InterruptSpinLock_TryLock(pw_sync_InterruptSpinLock* interrupt_spin_lock)
341
342 Invokes the ``InterruptSpinLock::try_lock`` member function on the given ``interrupt_spin_lock``.
343
344.. cpp:function:: void pw_sync_InterruptSpinLock_Unlock(pw_sync_InterruptSpinLock* interrupt_spin_lock)
345
346 Invokes the ``InterruptSpinLock::unlock`` member function on the given ``interrupt_spin_lock``.
347
348+--------------------------------------------+----------+-------------+-------+
349| *Safe to use in context* | *Thread* | *Interrupt* | *NMI* |
350+--------------------------------------------+----------+-------------+-------+
351| ``void pw_sync_InterruptSpinLock_Lock`` | ✔ | ✔ | |
352+--------------------------------------------+----------+-------------+-------+
353| ``bool pw_sync_InterruptSpinLock_TryLock`` | ✔ | ✔ | |
354+--------------------------------------------+----------+-------------+-------+
355| ``void pw_sync_InterruptSpinLock_Unlock`` | ✔ | ✔ | |
356+--------------------------------------------+----------+-------------+-------+
357
358
359Example in C
360^^^^^^^^^^^^
361.. code-block:: cpp
362
363 #include "pw_chrono/system_clock.h"
364 #include "pw_sync/interrupt_spin_lock.h"
365
366 pw::sync::InterruptSpinLock interrupt_spin_lock;
367
368 extern pw_sync_InterruptSpinLock interrupt_spin_lock; // This can only be created in C++.
369
370 void InterruptSafeCriticalSection(void) {
371 pw_sync_InterruptSpinLock_Lock(&interrupt_spin_lock);
372 NotThreadSafeCriticalSection();
373 pw_sync_InterruptSpinLock_Unlock(&interrupt_spin_lock);
374 }
375
376
377--------------------
378Signaling Primitives
379--------------------
380
381CountingSemaphore
382=================
383The CountingSemaphore is a synchronization primitive that can be used for
384counting events and/or resource management where receiver(s) can block on
385acquire until notifier(s) signal by invoking release.
386
387Note that unlike Mutexes, priority inheritance is not used by semaphores meaning
388semaphores are subject to unbounded priority inversions. Due to this, Pigweed
389does not recommend semaphores for mutual exclusion.
390
391The CountingSemaphore is initialized to being empty or having no tokens.
392
393The entire API is thread safe, but only a subset is interrupt safe. None of it
394is NMI safe.
395
396.. Warning::
397 Releasing multiple tokens is often not natively supported, meaning you may
398 end up invoking the native kernel API many times, i.e. once per token you
399 are releasing!
400
401BinarySemaphore
402===============
403BinarySemaphore is a specialization of CountingSemaphore with an arbitrary token
404limit of 1. Note that that ``max()`` is >= 1, meaning it may be released up to
405``max()`` times but only acquired once for those N releases.
406
407Implementations of BinarySemaphore are typically more efficient than the
408default implementation of CountingSemaphore.
409
410The BinarySemaphore is initialized to being empty or having no tokens.
411
412The entire API is thread safe, but only a subset is interrupt safe. None of it
413is NMI safe.