具有并发访问的地图


74

在具有并发访问的程序中使用映射时,是否需要在函数中使用互斥体来读取值?


3
如果严格来说它是只读映射,则不需要互斥体。
吉姆特,2012年

我不太清楚,因为会有设置和获取值的函数。

Answers:


105

读者众多,没有作家可以:

https://groups.google.com/d/msg/golang-nuts/HpLWnGTp-n8/hyUYmnWJqiQJ

一个作家,没有读者是可以的。(否则,地图不会太好。)

否则,如果至少有一个作家,而作家或读者至少还有一个,那么所有读者作家都必须使用同步来访问地图。互斥锁对此很好。


那么多位作家,没有读者呢?那会导致内存损坏吗?
user3125693 '18

@ user3125693可以。
hqt

54

sync.Map 截至2017年4月27日,已合并为Go master。

这是我们一直在等待的并发Map。

https://github.com/golang/go/blob/master/src/sync/map.go

https://godoc.org/sync#Map


2
好一个。请注意,新的sync.Map类型是专为仅追加的映射而设计的(因此,它不使用键的分片,这意味着,如果您的映射有很多搅动,则按照我上面的答案)。
orcaman '17

@OmarIlias我的意思是主要设置新值(不编辑或删除现有值)的情况。看到这个:github.com/golang/go/issues/20360
orcaman

1
@orcaman当前进行同步。即使在仅追加情况下,Map也可以比散列处理更快或更慢。这实际上取决于情况。使用尽可能多的原子操作,sync.Map内部结构可以比传统分片更快,因为与传统的哈希存储桶相比,它可以最大限度地减少锁定,但需要锁定。我确信会有基准测试,详细介绍了新sync.Map实现的痛点。但是假设速度较慢是不正确的,尤其是并发性。
Diegomontoya '17

1
@Diegomontoya当然是,这里是:medium.com/@deckarep/…,简而言之,仅当使用的核数大于4时,sync.map才会更快,否则,仅使用mutext将是4倍最大速度更快
John Balvin Arias

23

几天前,我在reddit主题中回答了您的问题:

在Go中,地图不是线程安全的。另外,例如,如果可能存在另一个goroutine正在写入同一数据(即),则数据甚至需要锁定才能读取。

通过在注释中的澄清判断,也将有setter函数,您的问题的答案是肯定的,您将必须使用互斥锁来保护您的读取。您可以使用RWMutex。例如,您可以查看我编写的表数据结构(实际上是在后台使用地图)的实现(实际上是在reddit线程中链接的那个)。


1
使用完整的读写器锁来获取与地图一样快的资源通常是浪费的
rmmh 2012年

2
您能详细说明一下吗?什么会更适合?

9
RW锁对于具有大量争用的资源很有用,但是它们比互斥锁具有更多的开销。映射获取/设置足够快,以至于程序可能没有足够的争用来使更复杂的锁比简单的互斥锁具有更好的吞吐量。
rmmh 2012年

3
谢谢你的澄清。您对此事有什么建议的文章吗?

2
但是,此博客的并发部分blog.golang.org/go-maps-in-action建议使用sync.RWMutex
fiorix 2014年

18

您可以使用并发映射为您处理并发难题。

// Create a new map.
map := cmap.NewConcurrentMap()

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

21
像这样的事情就是为什么我不能认真对待“不需要泛型”这一概念的原因。
Lucretiel '16

12
Go的概念并不是“不需要泛型”,而是“目前尚无实现泛型的干净方法,我们需要对此进行更多考虑”。例如,C ++会为正在使用的类型的所有可能组合生成代码,这将使编译时间和可执行文件的大小增加不合理的数量。
亚历山大

3

如果您只有一位作家,那么您可能可以避免使用原子值。以下内容是从https://golang.org/pkg/sync/atomic/#example_Value_readMostly改编而成(原始使用锁来保护写入,因此支持多个写入者)

type Map map[string]string
    var m Value
    m.Store(make(Map))

read := func(key string) (val string) { // read from multiple go routines
            m1 := m.Load().(Map)
            return m1[key]
    }

insert := func(key, val string) {  // update from one go routine
            m1 := m.Load().(Map) // load current value of the data structure
            m2 := make(Map)      // create a new map
            for k, v := range m1 {
                    m2[k] = v // copy all data from the current object to the new one
            }
            m2[key] = val // do the update that we need (can delete/add/change)
            m.Store(m2)   // atomically replace the current object with the new one
            // At this point all new readers start working with the new version.
            // The old version will be garbage collected once the existing readers
            // (if any) are done with it.
    }

0

为什么不使用Go并发模型呢,有一个简单的例子...

type DataManager struct {
    /** This contain connection to know dataStore **/
    m_dataStores map[string]DataStore

    /** That channel is use to access the dataStores map **/
    m_dataStoreChan chan map[string]interface{}
}

func newDataManager() *DataManager {
    dataManager := new(DataManager)
    dataManager.m_dataStores = make(map[string]DataStore)
    dataManager.m_dataStoreChan = make(chan map[string]interface{}, 0)
    // Concurrency...
    go func() {
        for {
            select {
            case op := <-dataManager.m_dataStoreChan:
                if op["op"] == "getDataStore" {
                    storeId := op["storeId"].(string)
                    op["store"].(chan DataStore) <- dataManager.m_dataStores[storeId]
                } else if op["op"] == "getDataStores" {
                    stores := make([]DataStore, 0)
                    for _, store := range dataManager.m_dataStores {
                        stores = append(stores, store)
                    }
                    op["stores"].(chan []DataStore) <- stores
                } else if op["op"] == "setDataStore" {
                    store := op["store"].(DataStore)
                    dataManager.m_dataStores[store.GetId()] = store
                } else if op["op"] == "removeDataStore" {
                    storeId := op["storeId"].(string)
                    delete(dataManager.m_dataStores, storeId)
                }
            }
        }
    }()

    return dataManager
}

/**
 * Access Map functions...
 */
func (this *DataManager) getDataStore(id string) DataStore {
    arguments := make(map[string]interface{})
    arguments["op"] = "getDataStore"
    arguments["storeId"] = id
    result := make(chan DataStore)
    arguments["store"] = result
    this.m_dataStoreChan <- arguments
    return <-result
}

func (this *DataManager) getDataStores() []DataStore {
    arguments := make(map[string]interface{})
    arguments["op"] = "getDataStores"
    result := make(chan []DataStore)
    arguments["stores"] = result
    this.m_dataStoreChan <- arguments
    return <-result
}

func (this *DataManager) setDataStore(store DataStore) {
    arguments := make(map[string]interface{})
    arguments["op"] = "setDataStore"
    arguments["store"] = store
    this.m_dataStoreChan <- arguments
}

func (this *DataManager) removeDataStore(id string) {
    arguments := make(map[string]interface{})
    arguments["storeId"] = id
    arguments["op"] = "removeDataStore"
    this.m_dataStoreChan <- arguments
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.