根据Go博客,
映射对于并发使用是不安全的:它没有定义当您同时读取和写入时会发生什么。如果您需要从同时执行的goroutine中读取和写入映射,则访问必须由某种同步机制调解。 (来源:https://blog.golang.org/go-maps-in-action)
有人可以详细说明吗?并发读取操作在例程中似乎是允许的,但是如果尝试读取和写入相同的密钥,则并发读/写操作可能会生成竞争条件。
在某些情况下可以降低最后的风险吗?例如:
这不是代码(显然),但我认为它显示了一个案例的大纲,即使A和B都试图访问m,也不会有竞争条件,或者如果有的话无关紧要因为有额外的限制。
答案 0 :(得分:36)
在Golang 1.6之前,并发读取没问题,并发写入不行,但写入和并发读取都可以。从Golang 1.6开始,映射在写入时无法读取。 所以在Golang 1.6之后,并发访问映射应该是:
package main
import (
"sync"
"time"
)
var m = map[string]int{"a": 1}
var lock = sync.RWMutex{}
func main() {
go Read()
time.Sleep(1 * time.Second)
go Write()
time.Sleep(1 * time.Minute)
}
func Read() {
for {
read()
}
}
func Write() {
for {
write()
}
}
func read() {
lock.RLock()
defer lock.RUnlock()
_ = m["a"]
}
func write() {
lock.Lock()
defer lock.Unlock()
m["b"] = 2
}
<强>增加:强>
您可以使用go run -race race.go
更改read
功能:
func read() {
// lock.RLock()
// defer lock.RUnlock()
_ = m["a"]
}
另一个选择:
众所周知,map
由存储桶实现,sync.RWMutex
将锁定所有存储桶。 concurrent-map使用fnv32
分隔密钥,每个分支使用一个sync.RWMutex
。
答案 1 :(得分:10)
并发读取(只读)没问题。并发写和/或读不行。
如果访问是同步的,则多个goroutine只能写入和/或读取相同的地图,例如通过sync
包,通道或其他方式。
你的例子:
- 函数A生成k并设置m [k] = 0。这是A写入映射m的唯一时间。已知k不在m。
- A将k传递给同时运行的功能B
- 然后读取m [k]。如果m [k] == 0,则它等待,仅在m [k]!= 0
时继续- B在地图中查找k。如果找到它,B将m [k]设置为某个正整数。如果不是,则等到k为m。
醇>
您的示例有2个goroutines:A和B,A尝试读取m
(在步骤3中),B尝试同时写入它(在步骤4中)。没有同步(你没有提及任何),所以不允许/不确定。
这是什么意思?未确定意味着即使B写m
,A也可能永远不会观察到变化。或者A可能会观察到甚至没有发生的变化。或者可能发生恐慌。或者由于这种非同步的并发访问,地球可能会爆炸(尽管后一种情况的可能性极小,甚至可能低于1e-40)。
相关问题:
what does not being thread safe means about maps in Go?
What is the danger of neglecting goroutine/thread-safety when using a map in Go?
答案 2 :(得分:6)
运行时添加了轻量级,尽力而为的并发检测 滥用地图。和往常一样,如果一个goroutine正在写一张地图,没有 其他goroutine应该同时读取或写入地图。如果 运行时检测到这种情况,它会打印诊断并崩溃 该程序。了解问题的最佳方法是运行 比赛探测器下的程序,将更可靠地识别 比赛并提供更多细节。
地图是复杂的,自我重组的数据结构。并发读写访问是未定义的。
没有代码,没有什么可说的。
答案 3 :(得分:2)
您可以使用sync.Map
,它可以安全地并发使用。唯一的警告是,您将放弃类型安全性,并更改对地图的所有读写操作,以使用为此类型定义的方法
答案 4 :(得分:1)
正如其他答案所述,原生map
类型不是goroutine
- 安全。阅读当前答案后的几个笔记:
答案 5 :(得分:0)
您可以在地图中存储指向int的指针,并且有多个goroutine读取指向的int,而另一个goroutine将新值写入int。在这种情况下,地图没有更新。
这不是Go的习惯,而不是你所要求的。
或者不是将键传递给地图,而是可以将索引传递给数组,并将其更新为一个goroutine,而其他人则读取该位置。
但您可能只是想知道为什么当密钥已经在地图中时,地图的价值无法使用新值进行更新。据推测,地图的散列方案没有任何改变 - 至少没有给出它们当前的实现。 Go的作者似乎不想为这些特殊情况提供补贴。一般来说,他们希望代码易于阅读和理解,并且当其他goroutine可以读取时不允许地图写入的规则使事情变得简单,现在在1.6中他们甚至可以开始在正常运行时捕获误用 - 节省了许多人的数小时调试。
答案 6 :(得分:0)
经过长时间的讨论,人们决定对地图的典型使用不需要从多个goroutine安全访问,在那些情况下,地图可能是某些已经同步的较大数据结构或计算的一部分。因此,要求所有映射操作都获取互斥量将减慢大多数程序的速度,并增加少数程序的安全性。但是,这并不是一个容易的决定,因为这意味着不受控制的地图访问可能会使程序崩溃。
该语言不排除原子图更新。在需要时,例如在托管不受信任的程序时,该实现可以互锁地图访问。
仅当正在进行更新时,地图访问才是不安全的。只要所有goroutine都仅读取(在地图中查找元素,包括使用for范围循环对其进行遍历),并且不通过分配元素或执行删除操作来更改地图,则它们可以安全地并发访问地图而无需同步。
为更正地图的使用,该语言的某些实现包含特殊检查,当并发执行不安全地修改地图时,该检查会在运行时自动报告。