我正在尝试了解其中一个Golang typical data races,其中从多个goroutine访问未受保护的全局变量可能会导致竞争条件:
var service map[string]net.Addr
func RegisterService(name string, addr net.Addr) {
service[name] = addr
}
func LookupService(name string) net.Addr {
return service[name]
}
接着说我们可以通过使用互斥锁保护它来解决这个问题:
var (
service map[string]net.Addr
serviceMu sync.Mutex
)
func RegisterService(name string, addr net.Addr) {
serviceMu.Lock()
defer serviceMu.Unlock()
service[name] = addr
}
func LookupService(name string) net.Addr {
serviceMu.Lock()
defer serviceMu.Unlock()
return service[name]
}
到目前为止,这么好。令我困惑的是:
this question的已接受答案表明,一个CPU绑定的goroutine会使已经多路复用到同一个OS线程的其他goroutine饿死(除非我们明确屈服于runtime.Gosched()
)。这是有道理的。
对我来说,上面的RegisterService()
和LookupService()
函数看起来是CPU绑定的,因为没有IO也没有产量。这是对的吗?
如果是,如果 GOMAXPROCS设置为1 ,那么上面例子中的互斥是否仍然是绝对必要的? goroutine在竞争条件可能发生时受CPU限制的事实不会照顾它吗?
即使它确实存在,我也认为在现实生活中使用互斥锁仍然是一个好主意,因为我们可能无法保证GOMAXPROCS的设置。还有其他原因吗?
答案 0 :(得分:0)
正如FUZxxl和Nick Craig-Wood所说,goroutines的当前行为是特定于实现的。所以,也许,读取或写入地图可以产生。考虑到maps are not thread safe,正确的并发访问需要互斥或其他同步。
作为互斥锁的替代方案,您可以生成一个goroutine,它可以在地图上执行所有操作并通过频道与之对话:
type writereq struct {
key string
value net.Addr
reply chan struct{}
}
type readreq struct {
key string
reply chan net.Addr
}
var service map[string]net.Addr
var reads = make(chan readreq)
var writes = make(chan writereq)
func RegisterService(name string, addr net.Addr) {
w := writereq{name, addr, make(chan struct{})}
writes <- w
return <-w.reply // return after registration confirmation
}
func LookupService(name string) net.Addr {
r := readreq{name, make(chan net.Addr)}
reads <- r
return <-r.reply
}
func serveRegistry() {
for {
select {
case r := <-reads:
r.reply <- service[r.name]
case w := <-writes:
service[w.name] = w.addr
w.reply <- struct{}
}
}
}