具有并发访问的映射表

92
在程序中使用具有并发访问的地图时,读取值的函数是否需要使用互斥锁?

4
如果它是一个只读地图,那么就不需要互斥锁。 - jimt
我不是很清楚,因为会有设置和获取值的函数。 - user1243746
6个回答

118

多个读者,无写者是可以的:

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

一个写者,无读者也可以。(否则,map就没什么用了。)

否则,如果至少有一个写者,并且至少还有一个写者或读者,那么所有读者和写者都必须使用同步方式访问map。互斥锁可用于此。


多个写入者,没有读取者怎么办?这会导致内存损坏吗? - user3125693
@user3125693 当然可以。 - hqt

67

2
不错。请注意,新的sync.Map类型是为追加操作而设计的(因此它不使用键分片,这意味着如果您的映射具有大量变化,则很可能会表现不佳,如我上面的回答所述)。 - orcaman
@OmarIlias 我的意思是在你主要设置新值的情况下(而不是编辑或删除现有值)。请参见此链接:https://github.com/golang/go/issues/20360 - orcaman
1
@orcaman 当前的go sync.Map在您的仅追加情况下可能比哈希分片更快或更慢。这实际上取决于具体情况。尽可能使用原子操作,sync.Map内部可以比传统分片更快,因为它最小化了锁定,而传统哈希桶则需要锁定。我相信将会进行基准测试,详细说明新的sync.Map实现的优点和缺点。但是假设它更慢是不正确的,特别是考虑到并发性。 - Diegomontoya
2
@Diegomontoya 当然,这是链接:https://medium.com/@deckarep/the-new-kid-in-town-gos-sync-map-de24a6bf7c2c,简而言之,如果使用的核心数大于4,则sync.map将更快,否则,仅使用互斥锁最多会快4倍。 - John Balvin Arias

25

我几天前在reddit 线程中回答了你的问题:

在Go中,映射不是线程安全的。此外,即使是读取数据,如果例如有另一个同时写入相同数据的goroutine,则需要锁定数据(并发情况下)。

根据您在评论中的澄清,您还将使用setter函数。因此回答您的问题是,是的,您必须使用互斥锁来保护读取;您可以使用RWMutex。例如,您可以查看我编写的表数据结构实现source(在幕后使用映射)的源代码。


1
通常情况下,对于像map这样访问速度很快的资源,使用完整的读写锁是浪费的。 - rmmh
2
你能详细说明一下吗?什么更适合呢? - user11617
9
读写锁适用于有很多争用的资源,但它们比互斥锁更加耗费系统资源。Map的读/写操作足够快,以至于程序可能没有足够的争用,使得使用更复杂的读写锁获得比简单互斥锁更好的吞吐量。 - rmmh
3
谢谢您的澄清。您有没有关于这个问题可以推荐的论文或文章? - user11617
2
然而,这篇文章的并发部分 http://blog.golang.org/go-maps-in-action 建议使用 sync.RWMutex。 - fiorix
显示剩余2条评论

23

您可以使用concurrent-map来处理并发问题。

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

26
这种事情是我无法认真对待“Go语言不需要泛型”的概念的原因。 - Lucretiel
12
这里的观点不是Go“不需要泛型”,而是“目前还没有干净利落的实现泛型的方式,我们需要再思考一下”。例如,C++会为使用的所有类型组合生成代码,这会使编译时间和可执行文件大小增加到不合理的程度。 - Alexander

3

如果您只有一个写入者,那么您可以使用原子值(atomic Value)。以下内容改编自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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接