Concurrency 使用共享地图的惯用方法很好
假设我有一个并发访问地图的程序,如下所示:Concurrency 使用共享地图的惯用方法很好,concurrency,go,Concurrency,Go,假设我有一个并发访问地图的程序,如下所示: func getKey(r *http.Request) string { ... } values := make(map[string]int) http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { key := getKey(r) fmt.Fprint(w, values[key]) }) http.HandleFunc("/set", func
func getKey(r *http.Request) string { ... }
values := make(map[string]int)
http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
key := getKey(r)
fmt.Fprint(w, values[key])
})
http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
key := getKey(r)
values[key] = rand.Int()
})
这很糟糕,因为映射写入是非原子的。所以我可以使用读/写互斥锁
func getKey(r *http.Request) string { ... }
values := make(map[string]int)
var lock sync.RWMutex
http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
key := getKey(r)
lock.RLock()
fmt.Fprint(w, values[key])
lock.RUnlock()
})
http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
key := getKey(r)
lock.Lock()
values[key] = rand.Int()
lock.Unlock()
})
这似乎很好,除了我们直接使用互斥体而不是通道
实现这一点的更为惯用的方法是什么?或者这是一个您真正需要的互斥锁的时刻吗?我认为这取决于您的性能期望以及最终将如何使用此映射 当我研究同一个问题时,我遇到了一个非常有用的问题 我个人的反应是,除非你真的觉得需要使用互斥,否则你应该默认使用通道。惯用围棋的中心点是,如果您坚持使用更高级的通道功能,就不需要使用互斥体,也不用担心锁定问题。记住Go的座右铭:“通过交流来分享记忆,不要通过分享记忆来交流。” 还有一个花絮,在MarkSummerfield的文章中,我们详细介绍了构建安全地图以供并发使用的各种技术 要突出显示Go的创建者之一: 并发简化了同步
- 不需要显式同步
- 程序的结构是隐式同步的
- 不能对消息队列使用锁本身。这就是频道的用途
- 您可以通过通道模拟锁,但通道不是用来模拟锁的
- 使用锁对共享资源进行并发安全访问
- 使用通道进行并发安全消息队列
使用RWMutex来保护映射写入。我想说,对于这个应用程序来说,互斥是很好的。用一种字体把它们包起来,这样你以后就可以像这样改变主意了。注意
sync.RWMutex
的嵌入,这使得锁定更加整洁
type thing struct {
sync.RWMutex
values map[string]int
}
func newThing() *thing {
return &thing{
values: make(map[string]int),
}
}
func (t *thing) Get(key string) int {
t.RLock()
defer t.RUnlock()
return t.values[key]
}
func (t *thing) Put(key string, value int) {
t.Lock()
defer t.Unlock()
t.values[key] = value
}
func main() {
t := newThing()
t.Put("hello", 1)
t.Put("sausage", 2)
fmt.Println(t.Get("hello"))
fmt.Println(t.Get("potato"))
}
这里有一种基于渠道的替代方法,使用渠道作为互斥机制:
func getKey(r *http.Request) string { ... }
values_ch := make(chan map[string]int, 1)
values_ch <- make(map[string]int)
http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
key := getKey(r)
values := <- values_ch
fmt.Fprint(w, values[key])
values_ch <- values
})
http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
key := getKey(r)
values := <- values_ch
values[key] = rand.Int()
values_ch <- values
})
func getKey(r*http.Request)字符串{…}
值\u ch:=make(chan-map[string]int,1)
这是一个非常恰当的答案和一个到文章的链接。我个人认为TS的方法对实现数据缓存很有用,在大多数其他情况下,重新设计应用程序以使用通道是完全可能的,这样可以消除很多头痛您永远不应该使用“默认通道”。:“使用最具表现力和/或最简单的方法。新手。过度使用频道。因为这是可能的。”所以“使用最具表现力的方法”-意思是先考虑一下。@Luke虽然我同意频道可能会被过度使用,但当你看看线程/锁定给程序员带来的主要困扰时,这就是为什么像Go这样的语言最初被创建的原因。实际上,线程并不难,难的是构建一个正确共享内存的程序,这就是为什么应该首先查看通道的原因。这意味着,如果渠道有效,就不要考虑其他选择。两者都应该平等对待。@Luke,相信我,Luke,我明白了。我把这篇文章交给OP的原因是为了让他们能够做出自己的判断。此外,我还就此事提供了我个人的回应,并将OP指向了一些额外的参考资料。你看,我一直在写同步锁定代码。要做到这一点并不容易,事实上你应该找个时间试试。请不要错误地引用我的话,显然互斥并不难使用。构建一个正确同步的程序是非常困难的,因此像Go这样的语言诞生了。但是,与RWMutex方法不同,在这里,即使没有编写者在场,读者也会相互阻止,不是吗?你能告诉我为什么他们会相互阻止吗?