cosmopolitan/third_party/nsync/cv.h
Justine Tunney 3f0bcdc3ef
Improve cancellations, randomness, and time
- Exhaustively document cancellation points
- Rename SIGCANCEL to SIGTHR just like BSDs
- Further improve POSIX thread cancellations
- Ensure asynchronous cancellations work correctly
- Elevate the quality of getrandom() and getentropy()
- Make futexes cancel correctly on OpenBSD 6.x and 7.x
- Add reboot.com and shutdown.com to examples directory
- Remove underscore prefix from awesome timespec_*() APIs
- Create assertions that help verify our cancellation points
- Remove bad timespec APIs (cmp generalizes eq/ne/gt/gte/lt/lte)
2022-11-05 23:45:32 -07:00

161 lines
6.9 KiB
C

#ifndef NSYNC_CV_H_
#define NSYNC_CV_H_
#include "third_party/nsync/mu.h"
#include "third_party/nsync/time.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define NSYNC_CV_INIT \
{ NSYNC_ATOMIC_UINT32_INIT_, 0 }
struct nsync_dll_element_s_;
struct nsync_note_s_;
/* An nsync_cv is a condition variable in the style of Mesa, Java,
POSIX, and Go's sync.Cond. It allows a thread to wait for a condition
on state protected by a mutex, and to proceed with the mutex held and
the condition true.
See also nsync_mu_wait() and nsync_mu_wait_with_deadline(), which
implement conditional critical sections. In many cases, they are
easier to use than condition variables.
Usage
After making the desired predicate true, call:
nsync_cv_signal (&cv); // If at most one thread can make use
// of the predicate becoming true.
or
nsync_cv_broadcast (&cv); // If multiple threads can make use
// of the predicate becoming true.
To wait for a predicate with no deadline (assuming
nsync_cv_broadcast() or nsync_cv_signal() is called whenever the
predicate becomes true):
nsync_mu_lock (μ)
while (!some_predicate_protected_by_mu) { // while-loop required
nsync_cv_wait (&cv, &mu);
}
// predicate is now true
nsync_mu_unlock (&mu);
To wait for a predicate with a deadline (assuming nsync_cv_broadcast() or
nsync_cv_signal() is called whenever the predicate becomes true):
nsync_mu_lock (&mu);
while (!some_predicate_protected_by_mu &&
nsync_cv_wait_with_deadline (&cv, &mu, abs_deadline,
cancel_note) == 0) {
}
if (some_predicate_protected_by_mu) { // predicate is true
} else {
// predicate is false, and deadline expired, or
// cancel_note was notified.
}
nsync_mu_unlock (&mu);
or, if the predicate is complex and you wish to write it just once
and inline, you could use the following instead of the for-loop
above:
nsync_mu_lock (&mu);
int pred_is_true = 0;
int outcome = 0;
while (!(pred_is_true = some_predicate_protected_by_mu) &&
outcome == 0) {
outcome = nsync_cv_wait_with_deadline (&cv, &mu, abs_deadline,
cancel_note);
}
if (pred_is_true) { // predicate is true
} else {
// predicate is false, and deadline expired, or
// cancel_note was notified.
}
nsync_mu_unlock (&mu);
As the examples show, Mesa-style condition variables require that
waits use a loop that tests the predicate anew after each wait. It
may be surprising that these are preferred over the precise wakeups
offered by the condition variables in Hoare monitors. Imprecise
wakeups make more efficient use of the critical section, because
threads can enter it while a woken thread is still emerging from the
scheduler, which may take thousands of cycles. Further, they make the
programme easier to read and debug by making the predicate explicit
locally at the wait, where the predicate is about to be assumed; the
reader does not have to infer the predicate by examining all the
places where wakeups may occur. */
typedef struct nsync_cv_s_ {
/* see bits below */
nsync_atomic_uint32_ word;
/* points to tail of list of waiters; under mu. */
struct nsync_dll_element_s_ *waiters;
} nsync_cv;
/* An nsync_cv should be zeroed to initialize, which can be accomplished
by initializing with static initializer NSYNC_CV_INIT, or by setting
the entire struct to 0, or using nsync_cv_init(). */
void nsync_cv_init(nsync_cv *cv);
/* Wake at least one thread if any are currently blocked on *cv. If the
chosen thread is a reader on an nsync_mu, wake all readers and, if
possible, a writer. */
void nsync_cv_signal(nsync_cv *cv);
/* Wake all threads currently blocked on *cv. */
void nsync_cv_broadcast(nsync_cv *cv);
/* Atomically release "mu" (which must be held on entry) and block the
caller on *cv. Wait until awakened by a call to nsync_cv_signal() or
nsync_cv_broadcast(), or a spurious wakeup; then reacquire "mu", and
return. Equivalent to a call to nsync_mu_wait_with_deadline() with
abs_deadline==nsync_time_no_deadline, and cancel_note==NULL. Callers
should use nsync_cv_wait() in a loop, as with all standard Mesa-style
condition variables. See examples above. Returns 0 normally, otherwise
ECANCELED may be returned if calling POSIX thread is cancelled only when
the PTHREAD_CANCEL_MASKED mode is in play. */
int nsync_cv_wait(nsync_cv *cv, nsync_mu *mu);
/* Atomically release "mu" (which must be held on entry) and block the
calling thread on *cv. It then waits until awakened by a call to
nsync_cv_signal() or nsync_cv_broadcast() (or a spurious wakeup), or
by the time reaching abs_deadline, or by cancel_note being notified.
In all cases, it reacquires "mu", and returns the reason for the call
returned (0, ETIMEDOUT, or ECANCELED). Use
abs_deadline==nsync_time_no_deadline for no deadline, and
cancel_note==NULL for no nsync cancellations (however POSIX thread
cancellations may still happen, and ECANCELED could still be returned
when the calling thread is cancelled only if PTHREAD_CANCEL_MASKED is
in play). wait_with_deadline() should be used in a loop, as with all
Mesa-style condition variables. See examples above.
There are two reasons for using an absolute deadline, rather than a
relative timeout---these are why pthread_cond_timedwait() also uses
an absolute deadline. First, condition variable waits have to be used
in a loop; with an absolute times, the deadline does not have to be
recomputed on each iteration. Second, in most real programmes, some
activity (such as an RPC to a server, or when guaranteeing response
time in a UI), there is a deadline imposed by the specification or
the caller/user; relative delays can shift arbitrarily with
scheduling delays, and so after multiple waits might extend beyond
the expected deadline. Relative delays tend to be more convenient
mostly in tests and trivial examples than they are in real
programmes. */
int nsync_cv_wait_with_deadline(nsync_cv *cv, nsync_mu *mu,
nsync_time abs_deadline,
struct nsync_note_s_ *cancel_note);
/* Like nsync_cv_wait_with_deadline(), but allow an arbitrary lock *v to be
used, given its (*lock)(mu) and (*unlock)(mu) routines. */
int nsync_cv_wait_with_deadline_generic(nsync_cv *cv, void *mu,
void (*lock)(void *),
void (*unlock)(void *),
nsync_time abs_deadline,
struct nsync_note_s_ *cancel_note);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* NSYNC_CV_H_ */