mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
3f0bcdc3ef
- 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)
161 lines
6.9 KiB
C
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_ */
|