aboutsummaryrefslogtreecommitdiff
path: root/docs/ref
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2024-10-20 22:24:17 -0700
committerGarrett D'Amore <garrett@damore.org>2024-10-20 22:24:17 -0700
commit976d62059097ef715687cbd0abb12ab6f957c882 (patch)
tree783c07df412297ae785b82cf7525ed76399df096 /docs/ref
parent9c9737ab0668d6f33cc842385b3f59238c672154 (diff)
downloadnng-976d62059097ef715687cbd0abb12ab6f957c882.tar.gz
nng-976d62059097ef715687cbd0abb12ab6f957c882.tar.bz2
nng-976d62059097ef715687cbd0abb12ab6f957c882.zip
Forgot to commit synch.md
Diffstat (limited to 'docs/ref')
-rw-r--r--docs/ref/api/synch.md182
1 files changed, 182 insertions, 0 deletions
diff --git a/docs/ref/api/synch.md b/docs/ref/api/synch.md
new file mode 100644
index 00000000..f32344b0
--- /dev/null
+++ b/docs/ref/api/synch.md
@@ -0,0 +1,182 @@
+# Synchronization Primitives
+
+In order to allow safely accessing shared state, or to allow coordination between
+different [threads][thread], _NNG_ provides {{i:synchronization primitives}} in the
+form of mutual exclusion locks and condition variables.
+
+Correct use of these primitives will be needed when accessing shared state from
+threads, or from callback functions associated with [asynchronous operations][aio].
+(The need to do this in callbacks is because the callback may be executed under
+a task thread other than the submitting thread.)
+
+## Mutual Exclusion Lock
+
+```c
+typedef struct nng_mtx nng_mtx;
+```
+
+Mutual exclusion locks, or {{i:mutex}} locks, represented by the {{i:`nng_mtx`}} structure,
+allow only a single thread to lock "own" the lock, acquired by [`nng_mtx_lock`][nng_mtx_lock].
+Any other thread trying to acquire the same mutex will wait until the owner has released the mutex
+by calling [`nng_mtx_unlock`][nng_mtx_unlock].
+
+### Creating a Mutex
+
+```c
+int nng_mutx_alloc(nng_mt **mtxp);
+```
+
+A mutex can be created by allocating one with {{i:`nng_mtx_lock`}}.
+On success, a pointer to the mutex is returned through _mtxp_.
+This function can fail due to insufficient memory or resources, in which
+case it will return `NNG_ENOMEM`. Otherwise it will succceed and return zero.
+
+### Destroying a Mutex
+
+```c
+void nng_mtx_free(nng_mtx *mtx);
+```
+
+When no longer needed, a mutex can be deallocated and its resources returned
+to the caller, by calling {{i:`nng_mtx_free`}}. The mutex must not be locked
+by any thread when calling this function.
+
+### Acquiring a Mutex
+
+```c
+void nng_mtx_lock(nng_mtx *mtx);
+```
+
+The {{i:`nng_mtx_lock`}} function acquires ownership of a mutex, waiting for it to
+unowned by any other threads if necessary.
+
+> [!IMPORTANT]
+> A thread must not attempt to reqacuire the same mutex while it already "owns" the mutex.
+> If it does attempt to do so, the result will be a single party deadlock.
+
+### Releasing a Mutex
+
+```c
+void nng_mtx_unlock(nng_mtx *mtx);
+```
+
+The {{i:`nng_mtx_unlock`}} function releases a mutex that the calling thread has previously
+acquired with [`nng_mtx_lock`][nng_mtx_lock].
+
+> [!IMPORTANT]
+> A thread must not attempt to release (unlock) a mutex if it was not the thread
+> that acquired the mutex to begin with.
+
+## Condition Variable
+
+```c
+typedef struct nng_cv nng_cv;
+```
+
+The {{i:`nng_cv`}} structure implements a {{i:condition variable}}, associated with the
+the [mutex][nng_mtx] _mtx_ which was supplied when it was created.
+
+Condition variables provide for a way to wait on an arbitrary condition, and to be woken
+when the condition is signaled.
+The mutex is dropped while the caller is asleep, and reacquired atomically when the caller
+is woken.
+
+> [!IMPORTANT]
+>
+> The caller of `nng_cv_until`, `nng_cv_wait`, `nng_cv_wake`, and `nng_cv_wake1` _must_
+> have ownership of the mutex _mtx_ when calling these functions.
+
+### Creating a Condition Variable
+
+```c
+int nng_cv_alloc(nng_cv **cvp, nng_mtx *mtx);
+```
+
+The {{i:`nng_cv_alloc`}} function allocates a condition variable, and associated with the mutex _mtx_,
+and returns a pointer to it in _cvp_.
+
+### Destroy a Condition Variable
+
+```c
+void nng_cv_free(nng_cv *cv);
+```
+
+The {{i:`nng_cv_free`}} function deallocates the condition variable _cv_.
+
+### Waiting for the Condition
+
+```c
+int nng_cv_until(nng_cv *cv, nng_time when);
+void nng_cv_wait(nng_cv *cv);
+```
+
+The {{i:`nng_cv_until`}} and {{i:`nng_cv_wait`}} functions put the caller to sleep until the condition
+variable _cv_ is signaled, or (in the case of `nng_cv_until`), the specified time _when_
+(as determined by [`nng_clock`][nng_clock] is reached.
+
+While `nng_cv_wait` never fails and so has no return value, the `nng_cv_until` function can
+return `NNG_ETIMEDOUT` if the time is reached before condition _cv_ is signaled by
+either [`nng_cv_wake`][nng_cv_wake] or [`nng_cv_wake1`][nng_cv_wake].
+
+### Signaling the Condition
+
+```c
+void nng_cv_wake(nng_cv *cv);
+void nng_cv_wake1(nng_cv *cv);
+```
+
+The {{i:`nng_cv_wake`}} and {{i:`nng_cv_wake1`}} functions wake threads waiting in
+[`nng_cv_until`][nng_cv_wait] or [`nng_cv_wait`][nng_cv_wait].
+The difference between these functions is that
+`nng_cv_wake` will wake _every_ thread, whereas `nng_cv_wake1` will wake up exactly
+one thread (which may be chosen randomly).
+
+> [!TIP]
+> Use of `nng_cv_wake1` may be used to reduce the "thundering herd" syndrom of waking
+> all threads concurrently, but should only be used in circumstances where the application
+> does not depend on _which_ thread will be woken. When in doubt, `nng_cv_wake` is safer.
+
+[aio]: aio.md
+[thread]: thread.md
+[nng_mtx]: #mutual-exclusion-lock
+[nng_mtx_lock]: #acquiring-a-mutex
+[nng_mtx_unlock]: #releasing-a-mutex
+[nng_cv]: #condition-variable
+[nng_cv_wait]: #waiting-for-the-condition
+[nng_cv_wake]: #signaling-the-condition
+[nng_clock]: ../util/nng_clock.md
+
+## Examples
+
+### Example 1: Allocating the condition variable
+
+```c
+ nng_mtx *m;
+ nng_cv *cv;
+ nng_mtx_alloc(&m); // error checks elided
+ nng_cv_alloc(&cv, m);
+```
+
+### Example 2: Waiting for the condition
+
+```c
+ expire = nng_clock() + 1000; // 1 second in the future
+ nng_mtx_lock(m); // assume cv was allocated using m
+ while (!condition_true) {
+ if (nng_cv_until(cv, expire) == NNG_ETIMEDOUT) {
+ printf("Time out reached!\n");
+ break;
+ }
+ }
+ // condition_true is true
+ nng_mtx_unlock(m);
+```
+
+### Example 3: Signaling the condition
+
+```c
+ nng_mtx_lock(m);
+ condition_true = true;
+ nng_cv_wake(cv);
+ nng_mtx_unlock(m);
+```