【问题标题】:Concurrent read/write of a map var snapshotmap var 快照的并发读/写
【发布时间】:2018-10-15 08:26:08
【问题描述】:

我遇到了我无法理解的情况。在我的代码中,我使用的函数需要读取地图(但不写入,仅循环遍历此地图中现有数据的快照)。有我的代码:

type MyStruct struct {
   *sync.RWMutex
   MyMap map[int]MyDatas
}

var MapVar = MyStruct{ &sync.RWMutex{}, make(map[int]MyDatas) }

func MyFunc() {
   MapVar.Lock()
   MapSnapshot := MapVar.MyMap
   MapVar.Unlock()
   for _, a := range MapSnapshot { // Map concurrent write/read occur here
      //Some stuff
   }
}

main() {
   go MyFunc()
}

函数“MyFunc”在一个goroutine中运行,只有一次,这个func没有多次运行。许多其他函数正在使用相同的方法访问相同的“MapVar”,并随机产生“映射并发写入/读取”。我希望有人能向我解释为什么我的代码是错误的。

感谢您的宝贵时间。

编辑:澄清一下,我只是在问为什么我的范围 MapSnapshot 会产生并发映射写入/读取。我无法理解如何同时使用此映射,因为我使用同步互斥体将真正的全局变量 (MapVar) 保存在本地变量 (MapSnapshot) 中。

编辑:已解决。要在不使用相同引用的情况下将映射的内容复制到新变量中(从而避免映射并发读/写),我必须遍历它并使用 for 循环将每个索引和内容写入新映射。

感谢 xpare 和 nilsocket。

【问题讨论】:

  • 我不明白你想要达到的目标,你能否解释得更清楚些。
  • 这是 100% 错误的。 MapSnapshot := MapVar.MyMap 确实生成可以与“原始”同时使用的副本。
  • 你说 MapVar.MyMap 的指针和 MapSnapshot 是一样的?它是一个新的 var 声明,所以它应该只是一个副本。你能解释一下为什么我在这一点上错了吗?
  • @Anarz 映射是一个复杂的数据结构,它不是整数或字符串值,可以通过单个赋值进行复制。它还包含对其他事物的其他几个引用,顺便说一句,map[x]x 本身就是一个引用。阅读this 了解更多信息。

标签: go


【解决方案1】:

这个函数没有多次运行。许多其他函数正在使用相同的方法访问相同的“MapVar”,并随机产生“map concurrent write/read”

当您将MapVar.MyMap 的值传递给MapSnapshot 时,Map concurrent write/read 将永远不会发生,因为该操作是用互斥体包装的。

但是在循环中,错误可能会发生,因为实际上读取过程是在循环期间发生的。所以最好用互斥锁来包装循环。

MapVar.Lock() // lock begin

MapSnapshot := MapVar.MyMap
for _, a := range MapSnapshot {
   // Map concurrent write/read occur here
   // Some stuff
}

MapVar.Unlock() // lock end

更新 1

以下是我对您的论点的回应:

这个for循环很费时间,这个循环里面东西很多,所以加锁会拖慢其他例程

根据你The function "MyFunc" is run in a go routine, only once, there is no multiple runs of this func的说法,那么我认为将MyFunc作为goroutine执行并不是一个好的选择。

为了提高性能,最好让循环内的进程在一个goroutine中执行。

func MyFunc() {
    for _, a := range MapVar.MyMap {
        go func(a MyDatas) {
           // do stuff here
        }(a)
    }
}

main() {
    MyFunc() // remove the go keyword
}

更新 2

如果你真的想复制 MapVar.MyMap 到另一个对象,将它传递给另一个变量将无法解决这个问题(mapintfloat32 或其他 primitive 类型)。

请参考本帖How to copy a map?

【讨论】:

  • 这个for循环需要很多时间,这个循环里面有很多东西,所以锁定会减慢其他例程。我正在寻找一个 MapVar.MyMap 的副本,它可以用作它并且不以任何方式链接到“原始” MapVar
  • 即使你复制了MapVar.MyMap,你也会通过对复制的对象执行循环来结束,所以map concurrent write/read仍然会发生。
  • MyFunc()的执行改为正常执行(不是goroutine),然后在循环内将每个进程作为goroutine运行,这样你会得到更好的性能。然后你可以删除互斥锁。
  • 所以我想我正在寻找将对象内容复制到新 var 而不是对象本身的正确方法
  • 我理解您的做法,并为此感谢。但是我绝对必须线程化这个函数,因为它在另一个函数中启动,这是一个大型软件的主要部分。因此,如果我理解:要复制地图的内容,我必须遍历它并将每个值写入新地图? Just NewMap := OldMap 永远不会工作,因为它还复制地图的引用,因为地图的本机数据结构不像任何标准字符串或 int 值
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-25
  • 1970-01-01
  • 2013-09-21
  • 1970-01-01
  • 1970-01-01
  • 2017-11-22
  • 1970-01-01
相关资源
最近更新 更多