cosmopolitan/third_party/nsync/mu_wait.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

116 lines
4.9 KiB
C

#ifndef NSYNC_MU_WAIT_H_
#define NSYNC_MU_WAIT_H_
#include "third_party/nsync/mu.h"
#include "third_party/nsync/time.h"
COSMOPOLITAN_C_START_
/* nsync_mu_wait() and nsync_mu_wait_with_deadline() can be used instead
of condition variables. In many straightforward situations they are
of equivalent performance and are somewhat easier to use, because
unlike condition variables, they do not require that the waits be
placed in a loop, and they do not require explicit wakeup calls.
Example:
Definitions:
static nsync_mu mu = NSYNC_MU_INIT;
static int i = 0; // protected by mu
// Condition for use with nsync_mu_wait().
static int int_is_zero (const void *v) {
return (*(const int *)v == 0);
}
Waiter:
nsync_mu_lock (&mu);
// Wait until i is zero.
nsync_mu_wait (&mu, &int_is_zero, &i, NULL);
// i is known to be zero here.
// ...
nsync_mu_unlock (&mu);
Thread potentially making i zero:
nsync_mu_lock (&mu);
i--;
// No need to signal that i may have become zero. The unlock call
// below will evaluate waiters' conditions to decide which to wake.
nsync_mu_unlock (&mu);
It is legal to use conditional critical sections and condition
variables on the same mutex.
--------------
The implementation benefits from determining whether waiters are
waiting for the same condition; it may then evaluate a condition once
on behalf of several waiters. Two waiters have equal condition if
their "condition" pointers are equal, and either:
- their "condition_arg" pointers are equal, or
- "condition_arg_eq" is non-null and (*condition_arg_eq)
(condition_arg0, condition_arg1) returns non-zero.
*condition_arg_eq will not be invoked unless the "condition" pointers
are equal, and the "condition_arg" pointers are unequal.
If many waiters wait for distinct conditions simultaneously,
condition variables may be faster.
*/
struct nsync_note_s_; /* forward declaration for an nsync_note */
/* Return when (*condition) (condition_arg) is true. Perhaps unlock and
relock *mu while blocked waiting for the condition to become true.
nsync_mu_wait() is equivalent to nsync_mu_wait_with_deadline() with
abs_deadline==nsync_time_no_deadline, and cancel_note==NULL.
Requires that *mu be held on entry. See nsync_mu_wait_with_deadline()
for more details on *condition and *condition_arg_eq. */
void nsync_mu_wait(nsync_mu *mu, int (*condition)(const void *condition_arg),
const void *condition_arg,
int (*condition_arg_eq)(const void *a, const void *b));
/* Return when at least one of: (*condition) (condition_arg) is true,
the deadline expires, or *cancel_note is notified. Perhaps unlock and
relock *mu while blocked waiting for one of these events, but always
return with *mu held. Return 0 iff the (*condition) (condition_arg)
is true on return, and otherwise either ETIMEDOUT or ECANCELED,
depending on why the call returned early. Callers should use
abs_deadline==nsync_time_no_deadline for no deadline, and
cancel_note==NULL for no cancellation.
Requires that *mu be held on entry.
The implementation may call *condition from any thread using the
mutex, and while holding *mu in either read or write mode; it
guarantees that any thread calling *condition will hold *mu in some
mode. Requires that (*condition) (condition_arg) neither modify state
protected by *mu, nor return a value dependent on state not protected
by *mu. To depend on time, use the abs_deadline parameter.
(Conventional use of condition variables have the same restrictions
on the conditions tested by the while-loop.) If non-null,
condition_arg_eq should return whether two condition_arg calls with
the same "condition" pointer are considered equivalent; it should
have no side-effects. */
int nsync_mu_wait_with_deadline(
nsync_mu *mu, int (*condition)(const void *condition_arg),
const void *condition_arg,
int (*condition_arg_eq)(const void *a, const void *b), int clock,
nsync_time abs_deadline, struct nsync_note_s_ *cancel_note);
/* Unlock *mu, which must be held in write mode, and wake waiters, if
appropriate. Unlike nsync_mu_unlock(), this call is not required to
wake nsync_mu_wait/nsync_mu_wait_with_deadline calls on conditions
that were false before this thread acquired the lock. This call
should be used only at the end of critical sections for which:
- nsync_mu_wait and/or nsync_mu_wait_with_deadline are in use on the same
mutex,
- this critical section cannot make the condition true for any of those
nsync_mu_wait/nsync_mu_wait_with_deadline waits, and
- when performance is significantly improved by using this call. */
void nsync_mu_unlock_without_wakeup(nsync_mu *mu);
COSMOPOLITAN_C_END_
#endif /* NSYNC_MU_WAIT_H_ */