sync - the locks tau routines share state with.
A pipe is the only thing the language gives that two routines can touch at the same time without racing, so everything here is a pipe underneath. A mutex is a pipe of one value: holding the lock means having put the token in, and unlocking means taking it back out, which is why a second Lock waits - the pipe is full.
As in Go, none of these may be copied once used: pass the object around, it is a reference. And as in Go, the first rule is that a pipe is usually the better answer. Reach for a lock only when the thing being shared is state rather than a message. For a single counter or flag, sync/atomic is smaller still: one instruction against a pipe, a wait and a wake.
Every field these objects use is given a value where the object is built, before any other routine can reach it. That is not tidiness: giving a field it never had grows the object, while assigning to one it already has only writes over it. Two routines growing the same object at once is a race no lock here would cover, since it is the lock itself being built. Anything added to this file has to keep that rule.
¶Mutex = fn()source
Mutex is a lock held by one routine at a time.
mu = sync.Mutex()
mu.Lock()
balance = balance + 1
mu.Unlock()
¶Lock = fn()source
Lock takes the lock, waiting until whoever holds it gives it back.
¶Unlock = fn()source
Unlock gives the lock back. Unlocking one that is not locked is a mistake and returns an error rather than waiting forever for a token that nobody put in.
¶RWMutex = fn()source
RWMutex is a lock any number of readers may hold together, but only one writer alone.
Readers can starve a writer: a writer waits for the last reader to leave, and readers that keep arriving never leave a gap. Use it where reads are many and writes are rare, which is the case it exists for.
¶RLock = fn()source
RLock takes the lock for reading. The first reader in takes the writer lock on behalf of all of them, the last one out gives it back.
¶RUnlock = fn()source
RUnlock gives back a reader's hold.
¶Lock = fn()source
Lock takes the lock for writing, waiting for every reader to be done.
¶Unlock = fn()source
Unlock gives back the writer's hold.
¶WaitGroup = fn()source
WaitGroup waits for a collection of tau routines to finish.
wg = sync.WaitGroup()
for i = 0; i < 3; i++ {
wg.Tau(fn() { work() })
}
wg.Wait()
¶Add = fn(delta)source
Add changes the count by delta, which may be negative. A count that would go below zero is a mistake and returns an error.
Reaching zero closes the pipe rather than sending on it: a close wakes every waiter at once, which is what "everyone is done" means. The next Add from zero makes a fresh one, so a group can be used again.
¶Done = fn()source
Done marks one of the routines as finished.
¶Tau = fn(f)source
Tau counts one more routine and starts it, so that the Add and the routine cannot drift apart. Go spells this one Go, after its own keyword; the keyword here is tau.
¶Wait = fn()source
Wait sleeps until the count is zero. Zero already, and it returns.
¶Once = fn()source
Once runs a thing once, however many routines ask for it.
once = sync.Once()
get = fn() { once.Do(fn() { conf = load() }); return conf }
¶Do = fn(f)source
Do calls f if no call to Do on this Once ever has. Callers arriving while f runs wait for it: when Do returns, f has returned.
f may not call Do on the same Once, that waits for a lock it holds.
¶OnceFunc = fn(f)source
OnceFunc returns a function that calls f only the first time it is called.
¶Do = fn(f)source
Do calls f if no call to Do on this Once ever has. Callers arriving while f runs wait for it: when Do returns, f has returned.
f may not call Do on the same Once, that waits for a lock it holds.
¶OnceValue = fn(f)source
OnceValue returns a function that calls f the first time it is called and returns what f returned, that same value every time after.