// Package memo provides a concurrency-unsafe// memoization of a function of type Func.packagememo// A Memo caches the results of calling a Func.typeMemostruct { f Func cache map[string]result}// Func is the type of the function to memoize.typeFuncfunc(key string) (interface{}, error)typeresultstruct { value interface{} err error}funcNew(f Func) *Memo {return&Memo{f: f, cache: make(map[string]result)}}// NOTE: not concurrency-safe!func (memo *Memo) Get(key string) (interface{}, error) { res, ok := memo.cache[key]if!ok { res.value, res.err = memo.f(key) memo.cache[key] = res }return res.value, res.err}
func (memo *Memo) Get(key string) (value interface{}, err error) { memo.mu.Lock() res, ok := memo.cache[key] memo.mu.Unlock()if!ok { res.value, res.err = memo.f(key)// Between the two critical sections, several goroutines// may race to compute f(key) and update the map. memo.mu.Lock() memo.cache[key] = res memo.mu.Unlock() }return res.value, res.err}
typeentrystruct { res result ready chanstruct{} // closed when res is ready}funcNew(f Func) *Memo {return&Memo{f: f, cache: make(map[string]*entry)}}typeMemostruct { f Func mu sync.Mutex// guards cache cache map[string]*entry}func (memo *Memo) Get(key string) (value interface{}, err error) { memo.mu.Lock() e := memo.cache[key]if e ==nil {// This is the first request for this key.// This goroutine becomes responsible for computing// the value and broadcasting the ready condition. e =&entry{ready: make(chanstruct{})} memo.cache[key] = e memo.mu.Unlock() e.res.value, e.res.err = memo.f(key)close(e.ready) // broadcast ready condition } else {// This is a repeat request for this key. memo.mu.Unlock()<-e.ready // wait for ready condition }return e.res.value, e.res.err}
// Func is the type of the function to memoize.typeFuncfunc(key string) (interface{}, error)// A result is the result of calling a Func.typeresultstruct { value interface{} err error}typeentrystruct { res result ready chanstruct{} // closed when res is ready}
// A request is a message requesting that the Func be applied to key.typerequeststruct { key string response chan<-result// the client wants a single result}typeMemostruct{ requests chanrequest }// New returns a memoization of f. Clients must subsequently call Close.funcNew(f Func) *Memo { memo :=&Memo{requests: make(chanrequest)}go memo.server(f)return memo}func (memo *Memo) Get(key string) (interface{}, error) { response :=make(chanresult) memo.requests <-request{key, response} res :=<-responsereturn res.value, res.err}func (memo *Memo) Close() { close(memo.requests) }
func (memo *Memo) server(f Func) { cache :=make(map[string]*entry)for req :=range memo.requests { e := cache[req.key]if e ==nil {// This is the first request for this key. e =&entry{ready: make(chanstruct{})} cache[req.key] = ego e.call(f, req.key) // call f(key) }go e.deliver(req.response) }}func (e *entry) call(f Func, key string) {// Evaluate the function. e.res.value, e.res.err =f(key)// Broadcast the ready condition.close(e.ready)}func (e *entry) deliver(response chan<-result) {// Wait for the ready condition.<-e.ready// Send the result to the client. response <- e.res}