Map 在Go中使用范围获取值不是线程安全的吗?
在具有并发写入程序(包括可以从映射中删除的写入程序)的映射Map 在Go中使用范围获取值不是线程安全的吗?,map,go,Map,Go,在具有并发写入程序(包括可以从映射中删除的写入程序)的映射m上进行测距时,这样做是否线程安全 for k, v := range m { ... } 为了线程安全,我需要防止其他可能的写入程序在我读取时更改值v,并且(当使用互斥锁时,因为锁定是一个单独的步骤)验证键k是否仍在映射中。例如: for k := range m { m.mutex.RLock() v, found := m[k] m.mutex.RUnlock() if found {
m
上进行测距时,这样做是否线程安全
for k, v := range m { ... }
为了线程安全,我需要防止其他可能的写入程序在我读取时更改值v
,并且(当使用互斥锁时,因为锁定是一个单独的步骤)验证键k
是否仍在映射中。例如:
for k := range m {
m.mutex.RLock()
v, found := m[k]
m.mutex.RUnlock()
if found {
... // process v
}
}
(假设其他写入程序在更改v
之前正在写入锁定m
)有更好的方法吗
编辑添加:我知道地图不是线程安全的。然而,根据Go规范at,它们在某种程度上是线程安全的(搜索“如果在迭代过程中删除了尚未到达的映射条目”)。此页面表明,使用
范围的代码不必担心其他goroutine插入地图或从地图中删除。我的问题是,这个线程安全性是否扩展到了v
,这样我就可以只使用k,v:=range m
而不使用其他线程安全机制来获取v
进行读取?我创建了一些测试代码,试图强制应用程序崩溃,以证明它不起作用,但即使运行明显的线程不安全代码(许多goroutines在没有锁定机制的情况下疯狂修改相同的map值),我也无法崩溃 不,映射操作不是原子/线程安全的,正如您问题的评论者所指出的那样
为了确保您的访问安全,我们鼓励您使用Go的作为资源访问令牌的一种方式。通道仅用于传递令牌。任何想要修改它的人都会请求通道阻塞或非阻塞。使用映射完成后,它将令牌传递回通道
迭代和使用映射应该足够简单和简短,因此您可以只使用一个令牌进行完全访问
如果情况并非如此,并且您使用映射来处理更复杂的内容/资源使用者需要更多的时间,那么您可以实现读写器访问令牌。因此,在任何给定时间,只有一个写入程序可以访问地图,但当没有任何写入程序处于活动状态时,令牌将传递给任意数量的读卡器,这些读卡器不会修改地图(因此它们可以同时读取)
有关通道的介绍,请参阅。您可以使用它来处理并发问题
// Create a new map.
map := cmap.NewConcurretMap()
// Add item to map, adds "bar" under key "foo"
map.Add("foo", "bar")
// Retrieve item from map.
tmp, ok := map.Get("foo")
// Checks if item exists
if ok == true {
// Map stores items as interface{}, hence we'll have to cast.
bar := tmp.(string)
}
// Removes item under key "foo"
map.Remove("foo")
只是一句评论(对于可能已经知道这一点的OP读者来说要比其他读者少):谢谢你的回复!我编辑了我的问题,以强调我想知道对于k,v:=range m{…}
而言,对于v
而言,仅用于阅读是否是线程安全的。Go规范指出,range
对于插入到映射或从映射中删除的其他goroutine来说是线程安全的。我知道频道,但是如果使用range
获取v
已经是线程安全的,那就是我所需要的。好吧,再看看,我发现你的答案是正确的。似乎为了线程安全,如果其他goroutine将写入(更新、删除或修改)正在迭代的相同密钥,则有必要在使用range
之前锁定整个映射。我提到的Go规范中的一节讨论了从它所覆盖的映射中删除或插入的单个线程,以及该线程如何影响自身。