Lorsque vous utilisez une carte dans un programme avec un accès simultané, est-il nécessaire d'utiliser un mutex dans les fonctions pour read values?
Plusieurs lecteurs, aucun auteur, ça va:
https://groups.google.com/d/msg/golang-nuts/HpLWnGTp-n8/hyUYmnWJqiQJ
Un auteur, pas de lecteurs, ça va. (Les cartes ne seraient pas très bonnes sinon.)
Sinon, s'il existe au moins un écrivain et au moins un écrivain ou un lecteur, les tous lecteurs et auteurs doivent utiliser la synchronisation pour accéder à la carte. Un mutex fonctionne bien pour cela.
sync.Map
a fusionné avec Go master le 27 avril 2017.
C'est la carte concurrente que nous attendions tous.
J'ai répondu à votre question dans this reddit thread il y a quelques jours:
En Go, les cartes ne sont pas thread-safe. De plus, les données doivent être verrouillées même pour lire si, par exemple, il pourrait y avoir une autre goroutine qui est écrire les mêmes données (en même temps).
À en juger par votre précision dans les commentaires, il y aura aussi des fonctions de réglage, la réponse à votre question est oui, vous devrez protéger vos lectures avec un mutex; vous pouvez utiliser un RWMutex . Pour un exemple, vous pouvez regarder le source de la mise en oeuvre d'une structure de données de table (utilise une carte dans les coulisses) que j'ai écrite (en réalité celle liée dans le fil rouge).
Vous pouvez utiliser concurrent-map pour gérer les douleurs liées à la concurrence.
// 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")
si vous n'avez qu'un seul auteur, vous pouvez probablement vous en tirer en utilisant une valeur atomique. Ce qui suit est adapté de https://golang.org/pkg/sync/atomic/#example_Value_readMostly (l’original utilise des verrous pour protéger l’écriture, prend en charge plusieurs auteurs)
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.
}
Ma simple implémentation:
import (
"sync"
)
type AtomicMap struct {
data map[string]string
rwLock sync.RWMutex
}
func (self *AtomicMap) Get(key string) (string, bool) {
self.rwLock.RLock()
defer self.rwLock.RUnlock()
val, found := self.data[key]
return val, found
}
func (self *AtomicMap) Set(key, val string) {
self.rwLock.Lock()
defer self.rwLock.Unlock()
self.data[key] = val
}
Pourquoi ne pas utiliser le modèle de concurrence Go à la place, voici un exemple simple ...
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
}