golang example go concurrency hashmap

golang - concurrenthashmap example



¿Qué tan seguros son los mapas de Golang para las operaciones de lectura/escritura concurrentes? (6)

Según el blog Go,

Los mapas no son seguros para el uso simultáneo: no está definido lo que sucede cuando lees y escribes en ellos simultáneamente. Si necesita leer y escribir en un mapa desde goroutines que se ejecutan simultáneamente, los accesos deben estar mediados por algún tipo de mecanismo de sincronización. (fuente: https://blog.golang.org/go-maps-in-action )

¿Alguien puede dar más detalles sobre esto? Las operaciones de lectura concurrente parecen permitidas en todas las rutinas, pero las operaciones de lectura / escritura concurrentes pueden generar una condición de carrera si uno intenta leer y escribir en la misma tecla.

¿Se puede reducir este último riesgo en algunos casos? Por ejemplo:

  • La función A genera k y establece m [k] = 0. Esta es la única vez que A escribe en el mapa m. Se sabe que k no está en m.
  • A pasa k a la función B que corre simultáneamente
  • A entonces lee m [k]. Si m [k] == 0, espera, continuando solo cuando m [k]! = 0
  • B busca k en el mapa. Si lo encuentra, B establece m [k] a algún entero positivo. Si no lo hace, espera hasta que k esté en m.

Esto no es un código (obviamente) pero creo que muestra las líneas generales de un caso en el que incluso si A y B intentan acceder a m no habrá una condición de carrera, o si la hay, no importará debido a la restricciones adicionales.


Go 1.6 Notas de la versión

El tiempo de ejecución ha agregado la detección liviana y de mejor esfuerzo del uso incorrecto concurrente de los mapas. Como siempre, si una goroutina está escribiendo en un mapa, ninguna otra goroutina debería leer o escribir el mapa al mismo tiempo. Si el tiempo de ejecución detecta esta condición, imprime un diagnóstico y bloquea el programa. La mejor manera de averiguar más sobre el problema es ejecutar el programa bajo el detector de carreras, que identificará la carrera de manera más confiable y brindará más detalles.

Los mapas son estructuras de datos complejas, auto-reorganizadas. El acceso simultáneo de lectura y escritura no está definido.

Sin código, no hay mucho más que decir.


Antes de Golang 1.6, la lectura concurrente está bien, la escritura concurrente no está bien, pero la escritura y la lectura concurrente están bien. Desde Golang 1.6, el mapa no se puede leer cuando se está escribiendo. Así que después de Golang 1.6, el mapa de acceso concurrente debería ser como:

package main import ( "sync" "time" ) var m = map[string]int{"a": 1} var lock = sync.RWMutex{} func main() { go Read() time.Sleep(1 * time.Second) go Write() time.Sleep(1 * time.Minute) } func Read() { for { read() } } func Write() { for { write() } } func read() { lock.RLock() defer lock.RUnlock() _ = m["a"] } func write() { lock.Lock() defer lock.Unlock() m["b"] = 2 }

O obtendrá el error a continuación:

ADICIONAL:

Puedes detectar la carrera usando go run -race race.go

Cambia la función de read :

func read() { // lock.RLock() // defer lock.RUnlock() _ = m["a"] }

Otra elección:

Como sabemos, el map fue implementado por cubos y sync.RWMutex bloqueará todos los sync.RWMutex . concurrent-map usa fnv32 para fnv32 la clave y cada depósito usa un sync.RWMutex .


Como indicaron las otras respuestas aquí, el tipo de map nativo no es goroutine -safe. Un par de notas después de leer las respuestas actuales:

  1. No use diferir para desbloquear, ya que tiene cierta sobrecarga que afecta el rendimiento (consulte this buena publicación). Llamar desbloquear directamente.
  2. Puede lograr un mejor rendimiento al reducir el tiempo dedicado entre los bloqueos. Por ejemplo, al fragmentar el mapa.
  3. Hay un paquete común (que se acerca a las 400 estrellas en GitHub) utilizado para resolver este llamado concurrent-map concurrent-map que tiene en mente el rendimiento y la facilidad de uso. Podría usarlo para manejar los problemas de concurrencia para usted.

Después de una larga discusión, se decidió que el uso típico de los mapas no requería el acceso seguro de múltiples goroutines, y en aquellos casos en los que lo hacía, el mapa probablemente formaba parte de una estructura de datos más grande o un cálculo que ya estaba sincronizado. Por lo tanto, exigir que todas las operaciones de mapas capturen un mutex ralentizaría la mayoría de los programas y agregaría seguridad a pocos. Sin embargo, no fue una decisión fácil, ya que significa que el acceso no controlado al mapa puede bloquear el programa.

El lenguaje no excluye las actualizaciones de mapas atómicos. Cuando sea necesario, como cuando se hospeda un programa que no es de confianza, la implementación podría interbloquear el acceso al mapa.

El acceso a los mapas no es seguro solo cuando se producen actualizaciones. Mientras todos los goroutines solo estén leyendo (buscando elementos en el mapa, incluida la iteración a través de él utilizando un ciclo de rango), y no cambiando el mapa mediante la asignación de elementos o haciendo eliminaciones, es seguro para ellos acceder al mapa simultáneamente sin sincronización.

Como una ayuda para corregir el uso del mapa, algunas implementaciones del lenguaje contienen una verificación especial que informa automáticamente en el tiempo de ejecución cuando un mapa se modifica de forma insegura por la ejecución concurrente.


La lectura concurrente (solo lectura) está bien. La escritura y / o lectura concurrente no está bien.

Múltiples goroutines solo pueden escribir y / o leer el mismo mapa si el acceso está sincronizado, por ejemplo, a través del paquete de sync , con canales o por otros medios.

Tu ejemplo

  1. La función A genera k y establece m [k] = 0. Esta es la única vez que A escribe en el mapa m. Se sabe que k no está en m.
  2. A pasa k a la función B que corre simultáneamente
  3. A entonces lee m [k]. Si m [k] == 0, espera, continuando solo cuando m [k]! = 0
  4. B busca k en el mapa. Si lo encuentra, B establece m [k] a algún entero positivo. Si no lo hace, espera hasta que k esté en m.

Su ejemplo tiene 2 goroutines: A y B, y A intenta leer m (en el paso 3) y B intenta escribirlo (en el paso 4) al mismo tiempo. No hay sincronización (no mencionó ninguna), por lo que esto solo no está permitido / no determinado.

Qué significa eso? No determinado significa que aunque B escriba m , A nunca puede observar el cambio. O A puede observar un cambio que ni siquiera sucedió. O puede ocurrir un pánico. O la Tierra puede explotar debido a este acceso concurrente no sincronizado (aunque la posibilidad de este último caso es extremadamente pequeña, tal vez incluso menor que 1e-40).

Preguntas relacionadas:

Mapa con acceso concurrente

¿Qué significa no ser seguro para los hilos en los mapas de Go?

¿Cuál es el peligro de descuidar la seguridad de goroutine / hilo cuando se usa un mapa en Go?


Puede almacenar un puntero a un int en el mapa, y hacer que múltiples goroutines lean el int al que se está apuntando, mientras que otro escribe un nuevo valor al int. El mapa no se está actualizando en este caso.

Esto no sería idiomático para Go y no lo que estabas preguntando.

O, en lugar de pasar una clave a un mapa, puede pasar el índice a una matriz, y un goroutine puede actualizarlo mientras otros leen la ubicación.

Pero probablemente se esté preguntando por qué el valor de un mapa no se puede actualizar con un nuevo valor cuando la clave ya está en el mapa. Es de suponer que no se está cambiando nada sobre el esquema de hashing del mapa, al menos no teniendo en cuenta su implementación actual. Parece que los autores de Go no quieren hacer concesiones para estos casos especiales. Por lo general, quieren que el código sea fácil de leer y entender, y una regla como no permitir escrituras en mapas cuando otros goroutines podrían estar leyendo hace las cosas simples y ahora en 1.6 incluso pueden comenzar a detectar el uso indebido durante los tiempos normales de ejecución, lo que ahorra a muchas personas muchas horas depuración