Volver a la página principal
lunes 21 octubre 2024
3

Cómo usar mapas concurrentes en Go

En Go, el acceso concurrente a estructuras de datos puede causar condiciones de carrera, lo que genera comportamientos inesperados. Los mapas concurrentes (concurrent maps) son una solución común para manejar de manera segura el acceso simultáneo a mapas desde múltiples goroutines. Aunque Go no tiene un tipo de mapa concurrente nativo, se puede lograr la concurrencia utilizando mecanismos como sync.Map o bloqueando el acceso a un mapa tradicional con sync.Mutex.

¿Qué es un mapa concurrente en Go?

Un mapa concurrente es una estructura de datos que permite a múltiples goroutines acceder y modificar un mapa al mismo tiempo sin causar conflictos o condiciones de carrera. Go ofrece la biblioteca sync para este propósito, donde puedes encontrar:

1. sync.Map: Un tipo de mapa diseñado específicamente para el acceso concurrente, simplificando su uso y proporcionando operaciones como Store, Load, y Delete.

2. sync.Mutex: Un mecanismo para bloquear manualmente el acceso a un mapa tradicional y garantizar que solo una goroutine pueda acceder al mapa a la vez.

Uso de sync.Map

sync.Map es una estructura de datos especial en Go que facilita el manejo de mapas concurrentes sin necesidad de bloquear manualmente.

Ejemplo básico con sync.Map:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var concurrentMap sync.Map

    // Almacenar valores en el mapa
    concurrentMap.Store("clave1", "valor1")
    concurrentMap.Store("clave2", "valor2")

    // Cargar valores del mapa
    valor, ok := concurrentMap.Load("clave1")
    if ok {
        fmt.Println("Clave1:", valor)
    }

    // Eliminar un valor del mapa
    concurrentMap.Delete("clave2")

    // Iterar sobre los elementos del mapa
    concurrentMap.Range(func(key, value interface{}) bool {
        fmt.Println(key, ":", value)
        return true
    })
}

En este ejemplo:

  • Store: Inserta o actualiza un valor en el mapa.
  • Load: Recupera un valor almacenado en el mapa.
  • Delete: Elimina un elemento del mapa.
  • Range: Permite iterar de manera concurrente sobre todos los elementos del mapa.

Uso de sync.Mutex con un mapa tradicional

Otra manera de hacer un mapa concurrente en Go es usar un mapa tradicional y proteger el acceso a él utilizando un mutex.

Ejemplo con sync.Mutex:

package main

import (
    "fmt"
    "sync"
)

type SafeMap struct {
    m   map[string]string
    mux sync.Mutex
}

func (sm *SafeMap) Store(key, value string) {
    sm.mux.Lock()
    sm.m[key] = value
    sm.mux.Unlock()
}

func (sm *SafeMap) Load(key string) (string, bool) {
    sm.mux.Lock()
    defer sm.mux.Unlock()
    value, ok := sm.m[key]
    return value, ok
}

func main() {
    safeMap := SafeMap{m: make(map[string]string)}

    // Almacenar valores
    safeMap.Store("clave1", "valor1")
    safeMap.Store("clave2", "valor2")

    // Cargar y mostrar un valor
    if valor, ok := safeMap.Load("clave1"); ok {
        fmt.Println("Clave1:", valor)
    }
}

En este ejemplo, el mutex garantiza que solo una goroutine pueda modificar o leer el mapa a la vez, evitando condiciones de carrera.

¿Cuándo usar sync.Map o sync.Mutex?

  • sync.Map: Es más fácil de usar, pero es más eficiente cuando tienes muchas más lecturas que escrituras. Se recomienda cuando hay muchas operaciones de lectura y menos operaciones de escritura en el mapa.
  • sync.Mutex: Proporciona mayor flexibilidad y control sobre el bloqueo. Es preferible cuando el uso del mapa implica un equilibrio más uniforme entre lecturas y escrituras.

Ejemplos adicionales

Uso concurrente con múltiples goroutines y sync.Map

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var concurrentMap sync.Map
    var wg sync.WaitGroup

    // Iniciar 5 goroutines que escriben al mapa
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            concurrentMap.Store(i, fmt.Sprintf("valor%d", i))
        }(i)
    }

    // Esperar que todas las goroutines terminen
    wg.Wait()

    // Iterar sobre los elementos del mapa
    concurrentMap.Range(func(key, value interface{}) bool {
        fmt.Println(key, ":", value)
        return true
    })
}

Este ejemplo inicia 5 goroutines que almacenan valores en el mapa de manera concurrente, utilizando sync.Map para evitar problemas de concurrencia.

Referencia oficial

Para más detalles sobre sync.Map y mecanismos de sincronización en Go, visita la documentación oficial de Go.

Etiquetas:
go
Compartir:
Creado por:
Author photo

Jorge García

Fullstack developer