Go by Example: Mutexes

En el ejemplo anterior vimos cómo manejar un simple estado de contador utilizando operaciones atómicas. Para un estado más complejo podemos usar un mutex para acceder de manera segura a los datos a través de múltiples goroutines.

package main
import (
    "fmt"
    "sync"
)

Container contiene un mapa de contadores; dado que queremos actualizarlo concurrentemente desde múltiples goroutines, añadimos un Mutex para sincronizar el acceso. Nota que los mutex no deben ser copiados, así que si este struct se pasa alrededor, debería hacerse por puntero.

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

Bloquea el mutex antes de acceder a counters; desbloquéalo al final de la función usando una declaración defer.

func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

Nota que el valor cero de un mutex se puede usar tal cual, por lo que no se requiere inicialización aquí.

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup

Esta función incrementa un contador nombrado en un bucle.

    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

Ejecuta varias goroutines concurrentemente; nota que todas acceden al mismo Container, y dos de ellas acceden al mismo contador.

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

Espera a que las goroutines terminen

    wg.Wait()
    fmt.Println(c.counters)
}

Ejecutar el programa muestra que los contadores se actualizaron como se esperaba.

$ go run mutexes.go
map[a:20000 b:10000]

A continuación, veremos cómo implementar esta misma tarea de gestión de estado usando solo goroutines y canales.

Siguiente ejemplo: Stateful Goroutines.