#ifndef NSYNC_MU_H_ #define NSYNC_MU_H_ #include "libc/intrin/dll.h" #include "third_party/nsync/atomic.h" COSMOPOLITAN_C_START_ /* An nsync_mu is a lock. If initialized to zero, it's valid and unlocked. An nsync_mu can be "free", held by a single thread (aka fiber, goroutine) in "write" (exclusive) mode, or by many threads in "read" (shared) mode. A thread that acquires it should eventually release it. It is illegal to acquire an nsync_mu in one thread and release it in another. It is illegal for a thread to reacquire an nsync_mu while holding it (even a second share of a "read" lock). Example usage: static struct foo { nsync_mu mu; // protects invariant a+b==0 on fields below. int a; int b; } p = { NSYNC_MU_INIT, 0, 0 }; // .... nsync_mu_lock (&p.mu); // The current thread now has exclusive access to p.a and p.b; // invariant assumed true. p.a++; p.b--; // restore invariant p.a+p.b==0 before releasing p.mu nsync_mu_unlock (&p.mu) Mutexes can be used with condition variables; see nsync_cv.h. nsync_mu_wait() and nsync_mu_wait_with_deadline() can be used instead of condition variables. See nsync_mu_wait.h for more details. Example use of nsync_mu_wait() to wait for p.a==0, using definition above: int a_is_zero (const void *condition_arg) { return (((const struct foo *)condition_arg)->a == 0); } ... nsync_mu_lock (&p.mu); nsync_mu_wait (&p.mu, &a_is_zero, &p, NULL); // The current thread now has exclusive access to // p.a and p.b, and p.a==0. ... nsync_mu_unlock (&p.mu); */ typedef struct nsync_mu_s_ { nsync_atomic_uint32_ word; /* internal use only */ int _zero; /* c pthread_mutex_t */ struct Dll *waiters; /* internal use only */ } nsync_mu; /* An nsync_mu should be zeroed to initialize, which can be accomplished by initializing with static initializer NSYNC_MU_INIT, or by setting the entire structure to all zeroes, or using nsync_mu_init(). */ #define NSYNC_MU_INIT \ { NSYNC_ATOMIC_UINT32_INIT_, 0 } void nsync_mu_init(nsync_mu *mu); /* Block until *mu is free and then acquire it in writer mode. Requires that the calling thread not already hold *mu in any mode. */ void nsync_mu_lock(nsync_mu *mu); /* Unlock *mu, which must have been acquired in write mode by the calling thread, and wake waiters, if appropriate. */ void nsync_mu_unlock(nsync_mu *mu); /* Attempt to acquire *mu in writer mode without blocking, and return non-zero iff successful. Return non-zero with high probability if *mu was free on entry. */ int nsync_mu_trylock(nsync_mu *mu); /* Block until *mu can be acquired in reader mode and then acquire it. Requires that the calling thread not already hold *mu in any mode. */ void nsync_mu_rlock(nsync_mu *mu); /* Unlock *mu, which must have been acquired in read mode by the calling thread, and wake waiters, if appropriate. */ void nsync_mu_runlock(nsync_mu *mu); /* Attempt to acquire *mu in reader mode without blocking, and return non-zero iff successful. Return non-zero with high probability if *mu was free on entry. Perhaps fail to acquire if a writer is waiting, to avoid starvation. */ int nsync_mu_rtrylock(nsync_mu *mu); /* May abort if *mu is not held in write mode by the calling thread. */ void nsync_mu_assert_held(const nsync_mu *mu); /* May abort if *mu is not held in read or write mode by the calling thread. */ void nsync_mu_rassert_held(const nsync_mu *mu); /* Return whether *mu is held in read mode. Requires that the calling thread holds *mu in some mode. */ int nsync_mu_is_reader(const nsync_mu *mu); COSMOPOLITAN_C_END_ #endif /* NSYNC_MU_H_ */