Synchronization Primitives
In order to allow safely accessing shared state, or to allow coordination between different threads, NNG provides 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. (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
typedef struct nng_mtx nng_mtx;
Mutual exclusion locks, or mutex locks, represented by the 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
int nng_mutx_alloc(nng_mt **mtxp);
A mutex can be created by allocating one with 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
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 nng_mtx_free
. The mutex must not be locked
by any thread when calling this function.
Acquiring a Mutex
void nng_mtx_lock(nng_mtx *mtx);
The 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
void nng_mtx_unlock(nng_mtx *mtx);
The 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
typedef struct nng_cv nng_cv;
The nng_cv
structure implements a 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
int nng_cv_alloc(nng_cv **cvp, nng_mtx *mtx);
The 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
void nng_cv_free(nng_cv *cv);
The nng_cv_free
function deallocates the condition variable cv.
Waiting for the Condition
int nng_cv_until(nng_cv *cv, nng_time when);
void nng_cv_wait(nng_cv *cv);
The nng_cv_until
and 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
void nng_cv_wake(nng_cv *cv);
void nng_cv_wake1(nng_cv *cv);
The nng_cv_wake
and 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 “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
nng_mtx *m;
nng_cv *cv;
nng_mtx_alloc(&m); // error checks elided
nng_cv_alloc(&cv, m);
Example 2: Waiting for the condition
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
nng_mtx_lock(m);
condition_true = true;
nng_cv_wake(cv);
nng_mtx_unlock(m);