Golang映射对并发读/写操作有多安全?

Joh*_* D. 35 concurrency hashmap go

根据Go博客,

映射对于并发使用是不安全的:它没有定义当您同时读取和写入时会发生什么.如果您需要从同时执行的goroutine中读取和写入映射,则访问必须由某种同步机制调解.(来源:https://blog.golang.org/go-maps-in-action)

任何人都可以详细说明这个吗?并发读取操作在例程中似乎是允许的,但是如果尝试读取和写入相同的键,则并发读/写操作可能会生成竞争条件.

在某些情况下可以降低最后的风险吗?例如:

  • 函数A生成k并设置m [k] = 0.这是A写入映射m的唯一时间.已知k不在m中.
  • A传递k到函数B并发运行
  • A然后读m [k].如果m [k] == 0,则它等待,仅在m [k]!= 0时继续
  • B在地图中寻找k.如果找到它,B将m [k]设置为某个正整数.如果不是,则等待直到k为m.

这不是代码(显然),但我认为它显示了一个案例的轮廓,即使A和B都试图访问m,也不会出现竞争条件,或者如果有的话也无关紧要因为额外的限制.

Bry*_*yce 47

在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
}
Run Code Online (Sandbox Code Playgroud)

或者你会得到以下错误: 在此输入图像描述

添加:

你可以通过使用来检测比赛 go run -race race.go

改变read功能:

func read() {
    // lock.RLock()
    // defer lock.RUnlock()
    _ = m["a"]
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

另一个选择:

众所周知,map由桶实现sync.RWMutex并将锁定所有桶.concurrent-map用于fnv32分割密钥,每个桶使用一个密钥sync.RWMutex.

  • > Golang 1.6之前,并发读可以,并发写不行,但是写并发读可以。这是完全错误的。同时读取和写入映射始终是不正确的。 (3认同)
  • 即使在1.6之前它并没有抱怨,并发读写也一直没问题. (2认同)

icz*_*cza 12

并发读取(只读)是可以的.并发写入和/或读取不正常.

如果访问是同步的,则多个goroutine只能写入和/或读取相同的映射,例如通过sync包,通道或通过其他方式.

你的例子:

  1. 函数A生成k并设置m [k] = 0.这是A写入映射m的唯一时间.已知k不在m中.
  2. A传递k到函数B并发运行
  3. A然后读m [k].如果m [k] == 0,则它等待,仅在m [k]!= 0时继续
  4. B在地图中寻找k.如果找到它,B将m [k]设置为某个正整数.如果不是,则等待直到k为m.

您的示例有2个goroutines:A和B,A尝试读取m(在步骤3中),B尝试同时写入它(在步骤4中).没有同步(你没有提及任何),因此不允许/不确定.

这是什么意思?未确定意味着即使B写m,A可能永远不会观察到变化.或者A可能会观察到甚至没有发生的变化.或者可能发生恐慌.或者地球可能由于这种非同步的并发访问而爆炸(尽管后一种情况的可能性极小,甚至可能小于1e-40).

相关问题:

映射并发访问

什么不是线程安全意味着Go中的地图?

在Go中使用地图时忽略goroutine /线程安全的危险是什么?


pet*_*rSO 7

去1.6发行说明

运行时添加了轻量级,尽力而为的并发滥用映射的方法.和往常一样,如果一个goroutine正在写一个地图,那么其他任何goroutine都不应该同时读取或写入地图.如果运行时检测到此情况,则会打印诊断并使程序崩溃.了解问题的最佳方法是在比赛检测器下运行程序,这将更可靠地识别比赛并提供更多细节.

地图是复杂的,自我重组的数据结构.并发读取和写入访问是未定义的.

没有代码,没有什么可说的.


小智 6

您可以使用sync.Map可以安全并发使用的方式。唯一需要注意的是,您将放弃类型安全并更改对映射的所有读取和写入以使用为此类型定义的方法

  • 请注意,“sync.Map”仅适用于锁争用最终成为瓶颈的特定用例。对于大多数常见情况,建议使用带有互斥锁的原生映射:“Map 类型是专门化的。大多数代码应该使用普通的 Go 映射,并具有单独的锁定或协调,以获得更好的类型安全性并更容易维护其他不变量以及地图内容。” https://golang.org/pkg/sync/#Map (8认同)

I.T*_*ger 5

经过长时间的讨论,我们决定映射的典型使用不需要从多个 goroutine 进行安全访问,并且在需要安全访问的情况下,映射可能是已经同步的某些较大数据结构或计算的一部分。因此,要求所有映射操作获取互斥锁会减慢大多数程序的速度,并增加少数程序的安全性。然而,这不是一个容易的决定,因为这意味着不受控制的地图访问可能会使程序崩溃。

\n\n

该语言并不排除原子地图更新。当需要时,例如托管不受信任的程序时,该实现可以互锁映射访问。

\n\n

仅当发生更新时地图访问才是不安全的。只要所有 goroutine 仅读取\xe2\x80\x94 查找映射中的元素,包括使用 for range 循环\xe2\x80\x94 进行迭代,并且不通过分配元素或执行删除来更改映射,那么它就是安全的让他们无需同步即可同时访问地图。

\n\n

作为正确映射使用的辅助手段,该语言的某些实现包含特殊检查,当并发执行不安全地修改映射时,该检查会在运行时自动报告。

\n