cosmopolitan/third_party/nsync/waiter.h
Justine Tunney 3c61a541bd
Introduce pthread_condattr_setclock()
This is one of the few POSIX APIs that was missing. It lets you choose a
monotonic clock for your condition variables. This might improve perf on
some platforms. It might also grant more flexibility with NTP configs. I
know Qt is one project that believes it needs this. To introduce this, I
needed to change some the *NSYNC APIs, to support passing a clock param.
There's also new benchmarks, demonstrating Cosmopolitan's supremacy over
many libc implementations when it comes to mutex performance. Cygwin has
an alarmingly bad pthread_mutex_t implementation. It is so bad that they
would have been significantly better off if they'd used naive spinlocks.
2024-09-02 23:45:42 -07:00

136 lines
5.2 KiB
C

#ifndef NSYNC_WAITER_H_
#define NSYNC_WAITER_H_
#include "third_party/nsync/time.h"
COSMOPOLITAN_C_START_
/* nsync_wait_n() allows the client to wait on multiple objects
(condition variables, nsync_notes, nsync_counters, etc.) until at
least one of them becomes ready, or a deadline expires.
It can be thought of as rather like Unix's select() or poll(), except
the the objects being waited for are synchronization data structures,
rather than file descriptors.
The client can construct new objects that can be waited for by
implementing three routines.
Examples:
To wait on two nsync_notes n0, n1, and a nsync_counter c0, with a
deadline of abs_deadline:
// Form an array of struct nsync_waitable_s, identifying the
// objects and the corresponding descriptors. (Static
// initialization syntax is used for brevity.)
static struct nsync_waitable_s w[] = {
{ &n0, &nsync_note_waitable_funcs },
{ &n1, &nsync_note_waitable_funcs },
{ &c0, &nsync_counter_waitable_funcs }
};
static struct nsync_waitable_s *pw[] = { &w[0], &w[1], &w[2] };
int n = sizeof (w) / sizeof (w[0]);
// Wait. The mu, lock, and unlock arguments are NULL because no
// condition variables are invovled.
int i = nsync_wait_n (NULL, NULL, NULL, abs_deadline, n, pw);
if (i == n) {
// timeout
} else {
// w[i].v became ready.
}
To wait on multiple condition variables, the mu/lock/unlock
parameters are used. Imagine cv0 and cv1 are signalled when
predicates pred0() (under lock mu0) and pred1() (under lock mu1)
become true respectively. Assume that mu0 is acquired before mu1.
static void lock2 (void *v) { // lock two mutexes in order
nsync_mu **mu = (nsync_mu **) v;
nsync_mu_lock (mu[0]);
nsync_mu_lock (mu[1]);
}
static void unlock2 (void *v) { // unlock two mutexes.
nsync_mu **mu = (nsync_mu **) v;
nsync_mu_unlock (mu[1]);
nsync_mu_unlock (mu[0]);
}
// Describe the condition variables and the locks.
static struct nsync_waitable_s w[] = {
{ &cv0, &nsync_cv_waitable_funcs },
{ &cv1, &nsync_cv_waitable_funcs }
};
static struct nsync_waitable_s *pw[] = { &w[0], &w[1] };
nsync_mu *lock_list[] = { &mu0, &mu1 };
int n = sizeof (w) / sizeof (w[0]);
lock2 (list_list);
while (!pred0 () && !pred1 ()) {
// Wait for one of the condition variables to be signalled,
// with no timeout.
nsync_wait_n (lock_list, &lock2, &unlock2,
nsync_time_no_deadline, n, pw);
}
if (pred0 ()) { ... }
if (pred1 ()) { ... }
unlock2 (list_list);
*/
/* forward declaration of struct that contains type dependent wait
operations */
struct nsync_waitable_funcs_s;
/* Clients wait on objects by forming an array of struct
nsync_waitable_s. Each each element points to one object and its
type-dependent functions. */
struct nsync_waitable_s {
/* pointer to object */
void *v;
/* pointer to type-dependent functions. Use
&nsync_note_waitable_funcs for an nsync_note,
&nsync_counternote_waitable_funcs for an nsync_counter,
&nsync_cv_waitable_funcs for an nsync_cv. */
const struct nsync_waitable_funcs_s *funcs;
};
/* Wait until at least one of *waitable[0,..,count-1] is has been
notified, or abs_deadline is reached. Return the index of the
notified element of waitable[], or count if no such element exists.
If mu!=NULL, (*unlock)(mu) is called after the thread is queued on
the various waiters, and (*lock)(mu) is called before return;
mu/lock/unlock are used to acquire and release the relevant locks
whan waiting on condition variables. */
int nsync_wait_n(void *mu, void (*lock)(void *), void (*unlock)(void *),
int clock, nsync_time abs_deadline, int count,
struct nsync_waitable_s *waitable[]);
/* A "struct nsync_waitable_s" implementation must implement these
functions. Clients should ignore the internals. */
struct nsync_waiter_s;
struct nsync_waitable_funcs_s {
/* Return the time when *v will be ready (max time if unknown), or 0
if it is already ready. The parameter nw may be passed as NULL, in
which case the result should indicate whether the thread would
block if it were to wait on *v. All calls with the same *v must
report the same result until the object becomes ready, from which
point calls must report 0. */
nsync_time (*ready_time)(void *v, struct nsync_waiter_s *nw);
/* If *v is ready, return zero; otherwise enqueue *nw on *v and return
non-zero. */
int (*enqueue)(void *v, struct nsync_waiter_s *nw);
/* If nw has been previously dequeued, return zero; otherwise dequeue
*nw from *v and return non-zero. */
int (*dequeue)(void *v, struct nsync_waiter_s *nw);
};
/* The "struct nsync_waitable_s" for nsync_note, nsync_counter, and nsync_cv. */
extern const struct nsync_waitable_funcs_s nsync_note_waitable_funcs;
extern const struct nsync_waitable_funcs_s nsync_counter_waitable_funcs;
extern const struct nsync_waitable_funcs_s nsync_cv_waitable_funcs;
COSMOPOLITAN_C_END_
#endif /* NSYNC_WAITER_H_ */