在Go语言中,使用range获取值是否是线程安全的?

huangapple go评论85阅读模式
英文:

Is getting a value using range not thread-safe in 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 {
        ... // 处理v
    }
}

(假设其他写入器在更改v之前会对m进行写锁定。)有更好的方法吗?

编辑以添加:我知道映射不是线程安全的。然而,根据Go规范中的一种方式,它们是线程安全的,参考http://golang.org/ref/spec#For_statements(搜索“如果在迭代期间删除尚未到达的映射条目”)。该页面指示使用range的代码不必担心其他goroutine插入或从映射中删除。我的问题是,这种线程安全性是否扩展到v,以便我可以仅使用for k, v := range m获取v(仅用于读取)而不使用其他线程安全机制?我创建了一些测试代码,试图强制应用程序崩溃以证明它不起作用,但即使运行明显不安全的代码(许多goroutine疯狂地修改相同的映射值而没有放置锁定机制),我也无法使Go崩溃!

英文:

When ranging over a map m that has concurrent writers, including ones that could delete from the map, is it not thread-safe to do this?:

for k, v := range m { ... }

I'm thinking to be thread-safe I need to prevent other possible writers from changing the value v while I'm reading it, and (when using a mutex and because locking is a separate step) verify that the key k is still in the map. For example:

for k := range m {
    m.mutex.RLock()
    v, found := m[k]
    m.mutex.RUnlock()
    if found {
        ... // process v
    }
}

(Assume that other writers are write-locking m before changing v.) Is there a better way?

Edit to add: I'm aware that maps aren't thread-safe. However, they are thread-safe in one way, according to the Go spec at http://golang.org/ref/spec#For_statements (search for "If map entries that have not yet been reached are deleted during iteration"). This page indicates that code using range needn't be concerned about other goroutines inserting into or deleting from the map. My question is, does this thread-safe-ness extend to v, such that I can get v for reading only using only for k, v := range m and no other thread-safe mechanism? I created some test code to try to force an app crash to prove that it doesn't work, but even running blatantly thread-unsafe code (lots of goroutines furiously modifying the same map value with no locking mechanism in place) I couldn't get Go to crash!

答案1

得分: 9

不,map操作不是原子/线程安全的,正如你问题的评论者指出的《为什么map操作不被定义为原子操作?》中所述。

为了保证安全访问,建议使用Go的**通道作为资源访问令牌的手段**。通道用于简单地传递令牌。任何想要修改它的人都会从通道中请求,可以是阻塞或非阻塞的方式。在完成对map的操作后,将令牌传回给通道。

对map进行迭代和操作应该足够简单和短暂,所以只使用一个令牌来进行完全访问应该是可以的。

如果情况不是这样的,如果你在map上进行更复杂的操作/资源消耗者需要更多时间,可以实现一个读者-写者访问令牌。因此,在任何给定时间,只有一个写者可以访问map,但当没有写者活动时,令牌将传递给任意数量的读者,他们不会修改map(因此可以同时读取)。

关于通道的介绍,请参阅《Effective Go文档中关于通道的部分》

英文:

No, map operations are not atomic/thread-safe, as the commenter to your question pointed to the golang FAQ “Why are map operations not defined to be atomic?”.

To secure your accessing it, you are encouraged to use Go's channels as a means of resource access token. The channel is used to simply pass around a token. Anyone wanting to modify it will request so from the channel - blocking or non-blocking. When done with working with the map it passes the token back to the channel.

Iterating over and working with the map should be sufficiently simple and short, so you should be ok using just one token for full access.

If that is not the case, and you use the map for more complex stuff/a resource consumer needs more time with it, you may implement a reader- vs writer-access-token. So at any given time, only one writer can access the map, but when no writer is active the token is passed to any number of readers, who will not modify the map (thus they can read simultaneously).

For an introduction to channels, see the Effective Go docs on channels.

答案2

得分: 0

你可以使用concurrent-map来处理并发问题。

// 创建一个新的map。
map := cmap.NewConcurretMap()

// 向map中添加项,在键“foo”下添加“bar”。
map.Add("foo", "bar")

// 从map中检索项。
tmp, ok := map.Get("foo")

// 检查项是否存在
if ok == true {
// Map将项存储为interface{},因此我们需要进行类型转换。
bar := tmp.(string)
}

// 删除键“foo”下的项。
map.Remove("foo")

英文:

You could use concurrent-map to handle the concurrency pains for you.

// 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")

huangapple
  • 本文由 发表于 2012年10月17日 23:49:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/12938233.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定