2012-06-16 16 views
55

Cuando utiliza un mapa en un programa con acceso simultáneo, ¿hay alguna necesidad de utilizar un mutex en las funciones para leer los valores?Mapa con acceso simultáneo

+3

Si es estrictamente un mapa de sólo lectura, un mutex no debería ser necesario. – jimt

+0

No estaba muy claro ya que habrá funciones para establecer y obtener valores. –

Respuesta

78

múltiples lectores, escritores no está bien:

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

Un escritor, no hay lectores está bien. (Los mapas no sería mucho más bueno de lo contrario.)

De lo contrario, si hay al menos un escritor y al menos uno más, ya sea escritor o lector, entonces todos los lectoresy escritores deben utilizar la sincronización para acceder al mapa . Un mutex funciona bien para esto.

20

me respondió su pregunta en this hilo reddit Hace pocos días:

En Go, los mapas no son seguros para subprocesos. Además, los datos requieren el bloqueo incluso para leyendo si, por ejemplo, podría haber otra goroutine que es escribiendo los mismos datos (al mismo tiempo, eso es).

A juzgar por su aclaración en los comentarios, que también van a haber funciones de colocador, la respuesta a su pregunta es sí, tendrá que proteger sus lecturas con un mutex; puede usar un RWMutex. Para un ejemplo, puede ver el source de la implementación de una estructura de datos de tablas (utiliza un mapa detrás de las escenas) que escribí (en realidad, el vinculado en el hilo de reddit).

+1

Por lo general, es un desperdicio utilizar un bloqueo de lector-escritor completo para un recurso tan rápido de acceder como un mapa – rmmh

+2

¿Puede elaborar un poco por favor? ¿Qué sería mejor? –

+9

Los bloqueos de RW son buenos para recursos con mucha controversia, pero tienen más sobrecarga que un mutex. Los get/sets del mapa son lo suficientemente rápidos como para que el programa no tenga suficiente contención para hacer que el bloqueo más complejo dé un mejor rendimiento que un simple mutex. – rmmh

16

Puede usar concurrent-map para controlar los problemas de concurrencia.

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

Cosas como esta son las razones por las que no puedo tomar en serio la noción de "no". necesitan genéricos ". – Lucretiel

+9

La idea no es que Go "no necesite genéricos", sino que "actualmente no hay una forma clara de implementar genéricos, tenemos que pensar un poco más". C++, por ejemplo, genera código para todas las posibles combinaciones de tipos que se utilizan, lo que aumenta los tiempos de compilación y los tamaños de los ejecutables en una cantidad irracional. – Alexander

1

si solo tiene un escritor, entonces probablemente pueda salirse con la suya con un valor atómico. La siguiente es una adaptación de https://golang.org/pkg/sync/atomic/#example_Value_readMostly (el original utiliza bloqueos para proteger la escritura, por lo que es compatible con varios escritores)

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

sync.Map ha fusionado a libre Maestro el 27 de abril, 2017.

Este es el mapa de concurrentes que todos han estado esperando.

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

https://godoc.org/sync#Map

+1

Bueno. Tenga en cuenta que el nuevo tipo de Sync.Map está diseñado para mapas de solo anexar (por esta razón no usa la fusión de claves, lo que significa que si su mapa tiene mucha rotación, lo más probable es que rinda por debajo de los mapas de estilo fragmentados según mi respuesta arriba). – orcaman

+0

@orcaman, ¿qué quieres decir con mapas de solo apéndice? – OmarIlias

+0

@OmarIlias me refiero a casos en los que establece principalmente nuevos valores (sin editar ni eliminar valores existentes). Vea esto: https://github.com/golang/go/issues/20360 – orcaman

0

¿Por qué no se hizo uso de Go modelo de concurrencia en su lugar, hay un ejemplo sencillo ...

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 
} 
Cuestiones relacionadas