我正在写一个并发安全的备忘录:
package mu
import (
"sync"
)
// Func represents a memoizable function, operating on a string key, to use with a Mu
type Func func(key string) interface{}
// Mu is a cache that memoizes results of an expensive computation
//
// It has a traditional implementation using mutexes.
type Mu struct {
// guards done
mu sync.RWMutex
done map[string]chan bool
memo map[string]interface{}
f Func
}
// Get a string key if it exists, otherwise computes the value and caches it.
//
// Returns the value and whether or not the key existed.
func (c *Mu) Get(key string) (interface{}, bool) {
c.mu.RLock()
_, ok := c.done[key]
c.mu.RUnlock()
if ok {
return c.get(key), true
}
c.mu.Lock()
_, ok = c.done[key]
if ok {
c.mu.Unlock()
} else {
c.done[key] = make(chan bool)
c.mu.Unlock()
v := c.f(key)
c.memo[key] = v
close(c.done[key])
}
return c.get(key), ok
}
// get returns the value of key, blocking on an existing computation
func (c *Mu) get(key string) interface{} {
<-c.done[key]
v, _ := c.memo[key]
return v
}
如您所见,有一个互斥体保护着 done
领域,这是使用
向其他 goroutines 发出信号,表明某个键的计算正在等待或已完成。这避免了对同一 key 的重复计算(调用 c.f(key)
)。
我的问题是关于这段代码的保证;通过确保计算 goroutine 在写入 c.memo
后关闭 channel , 这是否保证其他 goroutines 访问 c.memo[key]
在对 <-c.done[key]
的阻塞调用之后保证能看到计算结果?
最佳答案
简短的回答是是。
我们可以简化一些代码以了解原因的本质。考虑您的 Mu
结构:
type Mu struct {
memo int
done chan bool
}
我们现在可以定义 2 个函数,compute
和 read
func compute(r *Mu) {
time.Sleep(2 * time.Second)
r.memo = 42
close(r.done)
}
func read(r *Mu) {
<-r.done
fmt.Println("Read value: ", r.memo)
}
这里,compute
是一个计算量大的任务(我们可以通过 sleep 一段时间来模拟)
现在,在 main 函数中,我们启动一个新的 compute
go routine,并定期启动一些 read
go routines:
func main() {
r := &Mu{}
r.done = make(chan bool)
go compute(r)
// this one starts immediately
go read(r)
time.Sleep(time.Second)
// this one starts in the middle of computation
go read(r)
time.Sleep(2*time.Second)
// this one starts after the computation is complete
go read(r)
// This is to prevent the program from terminating immediately
time.Sleep(3 * time.Second)
}
在所有三种情况下,我们都打印出计算任务的结果。
Working code here
当您在 go 中“关闭” channel 时,所有等待 channel 结果的语句(包括在 channel 关闭后执行的语句)将被阻塞。因此,只要关闭 channel 的唯一地方是计算备忘录值的地方,您就可以得到保证。
唯一需要注意的地方是确保此 channel 未在代码中的其他任何地方关闭。
关于caching - channel 并发保证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50207652/