Map with concurrent access

MapGoMutex

Map Problem Overview


When you use a map in a program with concurrent access, is there any need to use a mutex in functions to read values?

Map Solutions


Solution 1 - Map

Multiple readers, no writers is okay:

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

One writer, no readers is okay. (Maps wouldn't be much good otherwise.)

Otherwise, if there is at least one writer and at least one more either writer or reader, then all readers and writers must use synchronization to access the map. A mutex works fine for this.

Solution 2 - Map

sync.Map has merged to Go master as of April 27, 2017.

This is the concurrent Map we have all been waiting for.

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

https://godoc.org/sync#Map

Solution 3 - Map

I answered your question in this reddit thread few days ago:

> In Go, maps are not thread-safe. Also, data requires locking even for > reading if, for example, there could be another goroutine that is > writing the same data (concurrently, that is).

Judging by your clarification in the comments, that there are going to be setter functions too, the answer to your question is yes, you will have to protect your reads with a mutex; you can use a RWMutex. For an example you can look at the source of the implementation of a table data structure (uses a map behind the scenes) which I wrote (actually the one linked in the reddit thread).

Solution 4 - Map

You could use concurrent-map to handle the concurrency pains for you.

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

Solution 5 - Map

if you only have one writer, then you can probably get away with using an atomic Value. The following is adapted from https://golang.org/pkg/sync/atomic/#example_Value_readMostly (the original uses locks to protect writing, so supports multiple writers)

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.
    }


   

Solution 6 - Map

Why no made use of Go concurrency model instead, there is a simple example...

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
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1243746View Question on Stackoverflow
Solution 1 - MapSoniaView Answer on Stackoverflow
Solution 2 - MapDiegomontoyaView Answer on Stackoverflow
Solution 3 - Mapuser11617View Answer on Stackoverflow
Solution 4 - MaporcamanView Answer on Stackoverflow
Solution 5 - MapMartyn WeberView Answer on Stackoverflow
Solution 6 - Mapuser3215378View Answer on Stackoverflow