τau / sync /

sync.tau

source
/Users/niconex/Documents/tau/stdlib/sync/sync.tau
1# sync - the locks tau routines share state with.2#3# A pipe is the only thing the language gives that two routines can touch at4# the same time without racing, so everything here is a pipe underneath. A5# mutex is a pipe of one value: holding the lock means having put the token6# in, and unlocking means taking it back out, which is why a second Lock waits7# - the pipe is full.8#9# As in Go, none of these may be copied once used: pass the object around, it10# is a reference. And as in Go, the first rule is that a pipe is usually the11# better answer. Reach for a lock only when the thing being shared is state12# rather than a message. For a single counter or flag, sync/atomic is smaller13# still: one instruction against a pipe, a wait and a wake.14#15# Every field these objects use is given a value where the object is built,16# before any other routine can reach it. That is not tidiness: giving a field17# it never had grows the object, while assigning to one it already has only18# writes over it. Two routines growing the same object at once is a race no19# lock here would cover, since it is the lock itself being built. Anything20# added to this file has to keep that rule.2122# Mutex is a lock held by one routine at a time.23#24#	mu = sync.Mutex()25#	mu.Lock()26#	balance = balance + 127#	mu.Unlock()28Mutex = fn() {29	m = new()30	m.tok = pipe(1)31	m.locked = false3233	# Lock takes the lock, waiting until whoever holds it gives it back.34	m.Lock = fn() {35		send(m.tok, 1)36		m.locked = true37	}3839	# Unlock gives the lock back. Unlocking one that is not locked is a40	# mistake and returns an error rather than waiting forever for a token41	# that nobody put in.42	m.Unlock = fn() {43		if !m.locked {44			return error("sync: unlock of unlocked mutex")45		}46		m.locked = false47		recv(m.tok)48	}4950	# ponytail: no TryLock. It needs a send that gives up instead of waiting,51	# and the language has no such send; reading m.locked would answer about52	# the past, not about the moment the lock is taken.5354	return m55}5657# RWMutex is a lock any number of readers may hold together, but only one58# writer alone.59#60# Readers can starve a writer: a writer waits for the last reader to leave,61# and readers that keep arriving never leave a gap. Use it where reads are62# many and writes are rare, which is the case it exists for.63RWMutex = fn() {64	rw = new()65	rw.w = Mutex() # held by the writer, or by the readers as a group66	rw.r = Mutex() # guards the count67	rw.readers = 06869	# RLock takes the lock for reading. The first reader in takes the writer70	# lock on behalf of all of them, the last one out gives it back.71	rw.RLock = fn() {72		rw.r.Lock()73		rw.readers++74		if rw.readers == 1 {75			rw.w.Lock()76		}77		rw.r.Unlock()78	}7980	# RUnlock gives back a reader's hold.81	rw.RUnlock = fn() {82		rw.r.Lock()83		if rw.readers == 0 {84			rw.r.Unlock()85			return error("sync: RUnlock of unlocked RWMutex")86		}87		rw.readers--88		if rw.readers == 0 {89			rw.w.Unlock()90		}91		rw.r.Unlock()92	}9394	# Lock takes the lock for writing, waiting for every reader to be done.95	rw.Lock = fn() { rw.w.Lock() }9697	# Unlock gives back the writer's hold.98	rw.Unlock = fn() { rw.w.Unlock() }99100	return rw101}102103# WaitGroup waits for a collection of tau routines to finish.104#105#	wg = sync.WaitGroup()106#	for i = 0; i < 3; i++ {107#		wg.Tau(fn() { work() })108#	}109#	wg.Wait()110WaitGroup = fn() {111	wg = new()112	wg.mu = Mutex()113	wg.n = 0114	wg.done = null # the pipe waiters sleep on, closed when the count is 0115116	# Add changes the count by delta, which may be negative. A count that117	# would go below zero is a mistake and returns an error.118	#119	# Reaching zero closes the pipe rather than sending on it: a close wakes120	# every waiter at once, which is what "everyone is done" means. The next121	# Add from zero makes a fresh one, so a group can be used again.122	wg.Add = fn(delta) {123		wg.mu.Lock()124		if wg.n + delta < 0 {125			wg.mu.Unlock()126			return error("sync: negative WaitGroup counter")127		}128		wg.n += delta129		if wg.n == 0 {130			if wg.done != null {131				close(wg.done)132				wg.done = null133			}134		} else if wg.done == null {135			wg.done = pipe()136		}137		wg.mu.Unlock()138	}139140	# Done marks one of the routines as finished.141	wg.Done = fn() { wg.Add(-1) }142143	# run is what Tau starts: f, and the Done that must follow it whatever f144	# does. It is a field because `tau` starts a call, and this is the call.145	wg.run = fn(f) {146		f()147		wg.Done()148	}149150	# Tau counts one more routine and starts it, so that the Add and the151	# routine cannot drift apart. Go spells this one Go, after its own152	# keyword; the keyword here is tau.153	wg.Tau = fn(f) {154		wg.Add(1)155		tau wg.run(f)156	}157158	# Wait sleeps until the count is zero. Zero already, and it returns.159	wg.Wait = fn() {160		wg.mu.Lock()161		d = wg.done162		wg.mu.Unlock()163		if d != null {164			recv(d)165		}166	}167168	return wg169}170171# Once runs a thing once, however many routines ask for it.172#173#	once = sync.Once()174#	get = fn() { once.Do(fn() { conf = load() }); return conf }175Once = fn() {176	o = new()177	o.mu = Mutex()178	o.done = false179180	# Do calls f if no call to Do on this Once ever has. Callers arriving181	# while f runs wait for it: when Do returns, f has returned.182	#183	# f may not call Do on the same Once, that waits for a lock it holds.184	o.Do = fn(f) {185		o.mu.Lock()186		if !o.done {187			o.done = true188			f()189		}190		o.mu.Unlock()191	}192193	return o194}195196# OnceFunc returns a function that calls f only the first time it is called.197OnceFunc = fn(f) {198	o = Once()199	return fn() { o.Do(f) }200}201202# OnceValue returns a function that calls f the first time it is called and203# returns what f returned, that same value every time after.204OnceValue = fn(f) {205	o = Once()206	v = new()207	v.val = null208	return fn() {209		o.Do(fn() { v.val = f() })210		return v.val211	}212}