τau /

sync

module
sync = import("sync")

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.