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}