【问题标题】:How to lock a specific map's index for concurent Read/Write in Golang如何锁定特定地图索引以在 Golang 中进行并发读/写
【发布时间】:2019-04-17 15:23:49
【问题描述】:

我想知道是否有办法在并发读/写期间仅锁定映射中的索引。如果答案很明显,我对 Golang 和并行性很抱歉。

func Check(a, b []string) map[string]int {
    var res = make(map[string]int)
    go func() {
        for _, v := range a {
            res[v]++
        }
    }()
    go func() {
        for _, v := range b {
            res[v]++
        }
    }()
    return res
}

由于并发映射读/写,最终这段代码会出现恐慌。所以我们应该添加互斥锁来锁定地图。

var m sync.Mutex
go func() {
    for _, v := range a {
        m.Lock()
        res[v]++
        m.Unlock()
    }
}()
go func() {
    for _, v := range b {
        m.Lock()
        res[v]++
        m.Unlock()
    }
}()

但据我了解m.lock() 会锁定我的整个地图吗?锁定所有内容不是太多开销吗?这让我很烦恼,因为我认为这段代码可能不会比线性运行更快。我是否可以只锁定map["some key"] 的地图,以便我的第二个goroutine 仍然可以写入map["some other key"]

【问题讨论】:

  • 您不能只锁定地图的一个条目,因为编写另一个条目可能会导致内部重组,这也可能影响其他条目,包括您打算锁定的条目。
  • 事实上你也不能锁定整个地图。您锁定互斥锁。互斥锁控制访问的内容完全取决于您如何使用它。您可以为映射中的每个键创建一个互斥体,这将允许您按条目锁定粒度,但正如 icza 指出的那样,这实际上并不能防止数据竞争。
  • 谢谢!您的回复对我帮助很大!

标签: dictionary go concurrency parallel-processing mutex


【解决方案1】:

来自GO 1.9 Release notes

并发地图

sync 包中的新 Map 类型是具有分摊常数时间加载、存储和删除的并发映射。多个 goroutine 同时调用 Map 的方法是安全的。

GO 团队为您打造了一款!

【讨论】:

    【解决方案2】:

    Map 本身不处理锁,因此在多个 go 例程上对它们的任何操作(或在操作它们时读取)都需要一些 for 同步(例如,sync.Mutex)。不过,您可以做一些更有趣的事情。

    RW 互斥体

    您可以根据您的用例变得更花哨,并使用sync.RWMutex。这将允许并发读取,同时安全地阻止任何写入。例如:

    package main
    
    import (
        "sync"
        "time"
    )
    
    func main() {
        m := map[int]int{}
        lock := sync.RWMutex{}
    
        go func() {
            // Writer
            for range time.Tick(250 * time.Millisecond) {
                // Notice that this uses Lock and NOT RLock
                lock.Lock()
                m[5]++
                m[6] += 2
                lock.Unlock()
            }
        }()
    
        go func() {
            for range time.Tick(250 * time.Millisecond) {
                lock.RLock()
                println(m[5])
                lock.RUnlock()
            }
        }()
    
        for range time.Tick(250 * time.Millisecond) {
            lock.RLock()
            println(m[6])
            lock.RUnlock()
        }
    }
    

    不过,这并没有给你一个按键锁定机制。

    sync.Map

    sync.Map 由标准库提供,并且很健壮。它具有更细粒度的锁定。

    package main
    
    import (
        "sync"
        "time"
    )
    
    func main() {
        m := sync.Map{}
    
        go func() {
            // Writer
            for range time.Tick(250 * time.Millisecond) {
                value, _ := m.LoadOrStore(5, 0)
                m.Store(5, value.(int)+1)
                value, _ = m.LoadOrStore(6, 0)
                m.Store(6, value.(int)+2)
            }
        }()
    
        go func() {
            for range time.Tick(250 * time.Millisecond) {
                value, _ := m.LoadOrStore(5, 0)
                println(value.(int))
            }
        }()
    
        for range time.Tick(250 * time.Millisecond) {
            value, _ := m.LoadOrStore(6, 0)
            println(value.(int))
        }
    }
    

    请注意,代码没有任何互斥体。另请注意,您要处理空接口...

    【讨论】:

      猜你喜欢
      • 2019-12-20
      • 1970-01-01
      • 2022-06-20
      • 1970-01-01
      • 2021-08-03
      • 2012-07-29
      • 1970-01-01
      • 1970-01-01
      • 2018-01-17
      相关资源
      最近更新 更多