【问题标题】:What happens if concurrent processes write to a global variable the same value?如果并发进程向全局变量写入相同的值会发生什么?
【发布时间】:2018-08-18 16:24:14
【问题描述】:

我只是想知道同时将相同的值写入全局变量是否会导致损坏。我的大脑告诉我这并没有错,因为它只是内存中的一个位置,但我认为我应该仔细检查这个假设。

我有并发进程写入全局映射var linksToVisit map[string]bool。该地图实际上是在跟踪网站上的哪些链接需要进一步抓取。

但是,并发进程可能在它们各自的页面上具有相同的链接,因此每个进程都会同时将相同的链接标记为true。在这种情况下不使用锁没有任何问题,对吧?注意:我从不将值改回false,所以要么键存在且其值为真,要么不存在。

var linksToVisit = map[string]bool{}

... 
// somewhere later a goroutine finds a link and marks it as true
// it is never marked as false anywhere
linksToVisit[someLink] = true 

【问题讨论】:

  • 当你同时插入一个key的时候,肯定是有问题的。地图的底层结构可能会增长和变化,从而导致混乱。当您使用相同的布尔值写入相同的键时,它可能不是那么糟糕,但由于编译器实现细节,它仍然可能是未定义的行为。在这里阅读更多:golang.org/ref/mem
  • Go 还有一个比赛检测器。 blog.golang.org/race-detector
  • @leafbebop 是的,我在问了这个问题后才意识到这种情况。如果您想将其放在答案中,我会接受。

标签: go concurrency goroutine


【解决方案1】:

如果并发进程写入全局变量会发生什么 相同的值?

数据竞争的结果未定义。

运行 Go 数据竞争检测器。

参考资料:

Wikipedia: Race condition

Benign Data Races: What Could Possibly Go Wrong?

The Go Blog: Introducing the Go Race Detector

Go: Data Race Detector


Go 1.8 Release Notes

Concurrent Map Misuse

在 Go 1.6 中,运行时添加了轻量级、尽力而为的检测 同时滥用地图。此版本改进了该检测器 支持检测同时写入和迭代的程序 在地图上。

与往常一样,如果一个 goroutine 正在写入 map,则没有其他 goroutine 应该是读取(包括迭代)或写入地图 同时。如果运行时检测到这种情况,它会打印一个 诊断和崩溃程序。了解更多信息的最佳方式 问题是在比赛检测器下运行程序,这将 更可靠地识别种族并提供更多细节。


例如,

package main

import "time"

var linksToVisit = map[string]bool{}

func main() {
    someLink := "someLink"
    go func() {
        for {
            linksToVisit[someLink] = true
        }
    }()
    go func() {
        for {
            linksToVisit[someLink] = true
        }
    }()
    time.Sleep(100 * time.Millisecond)
}

输出:

$ go run racer.go
fatal error: concurrent map writes
$

$ go run -race racer.go

==================
WARNING: DATA RACE
Write at 0x00c000078060 by goroutine 6:
  runtime.mapassign_faststr()
      /home/peter/go/src/runtime/map_faststr.go:190 +0x0
  main.main.func2()
      /home/peter/gopath/src/racer.go:16 +0x6a

Previous write at 0x00c000078060 by goroutine 5:
  runtime.mapassign_faststr()
      /home/peter/go/src/runtime/map_faststr.go:190 +0x0
  main.main.func1()
      /home/peter/gopath/src/racer.go:11 +0x6a

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:14 +0x88

Goroutine 5 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:9 +0x5b
==================

fatal error: concurrent map writes

$

【讨论】:

    【解决方案2】:

    如果您使用多个 go 例程同时更改相同的值,最好使用锁。因为当另一个函数发生变化时,互斥锁和锁被用于保护值不被访问,就像在访问同一个表时写入数据库表一样。

    关于使用具有不同键的地图的问题,在 Go 中并不可取:

    地图的典型使用不需要多个安全访问 goroutines,在这种情况下,地图可能是其中的一部分 一些更大的数据结构或计算 同步。因此要求所有映射操作都获取一个互斥锁 会减慢大多数程序并为少数程序增加安全性。

    只有在发生更新时,地图访问才是不安全的。只要所有 goroutines 只是读取——在 map 中查找元素,包括 使用 for range 循环遍历它——并且不改变地图 通过分配给元素或进行删除,它们是安全的 无需同步即可同时访问地图。

    所以不建议更新地图。欲了解更多信息Check FAQ,了解为什么映射操作未定义为原子操作。

    还注意到,如果你真的想去,应该有一种同步它们的方法。

    地图对于并发使用是不安全的:它没有定义会发生什么 当您同时读取和写入它们时。如果你需要阅读 从并发执行的goroutines中写入和写入映射, 访问必须通过某种同步机制进行调解。 保护地图的一种常用方法是使用 sync.RWMutex。

    【讨论】:

    • 你能分享你获得该文档的链接吗?
    【解决方案3】:

    并发映射写入不正常,因此您很可能会遇到致命错误。所以我认为应该使用锁

    【讨论】:

    • 不知道为什么 -1... 我没有遇到致命错误,但是您是说并发写入映射是不合适的,因为并发进程可能会写入不同的键?或者你是说即使在同一个键上并发映射写入也不行?
    • 好吧,当并发写入发生时,行为是未定义的,我想没有人喜欢这样吧?
    【解决方案4】:

    从 Go 1.6 开始,同时映射写入将导致 panic。使用sync.Map 同步访问。

    查看地图赋值实现: https://github.com/golang/go/blob/fe8a0d12b14108cbe2408b417afcaab722b0727c/src/runtime/hashmap.go#L519

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-12
      • 1970-01-01
      • 1970-01-01
      • 2012-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-20
      相关资源
      最近更新 更多