1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
# 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`].
Any other thread trying to acquire the same mutex will wait until the owner has released the mutex
by calling [`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`].
> [!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] _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`] 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`] or [`nng_cv_wake1`].
### 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`] or [`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 "{{i: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.
## 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);
```
## See Also
[Threads][thread],
[Time][time],
[Asynchronous I/O][aio]
{{#include ../xref.md}}
|