Go by Example: Mutex

Nell’esempio precedente abbiamo visto come gestire semplici stati di contatori usando operazioni atomiche. Per stati più complessi possiamo usare un mutex per accedere ai dati in modo sicuro attraverso più goroutine.

package main
import (
    "fmt"
    "sync"
)

Container contiene una mappa di contatori; dato che vogliamo aggiornarla simultaneamente da più goroutine, aggiungiamo un Mutex per sincronizzare l’accesso. Nota che i mutex non devono essere copiati, quindi se questa struct viene passata in giro, dovrebbe essere fatto tramite puntatore.

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

Blocchiamo il mutex prima di accedere a counters; lo sblocchiamo alla fine della funzione usando un’istruzione defer.

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

Nota che il valore zero di un mutex è utilizzabile così com’è, quindi non è richiesta alcuna inizializzazione qui.

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

Questa funzione incrementa un contatore nominato in un ciclo.

    doIncrement := func(name string, n int) {
        for range n {
            c.inc(name)
        }
    }

Eseguiamo diverse goroutine simultaneamente; nota che tutte accedono allo stesso Container, e due di esse accedono allo stesso contatore.

    wg.Go(func() {
        doIncrement("a", 10000)
    })
    wg.Go(func() {
        doIncrement("a", 10000)
    })
    wg.Go(func() {
        doIncrement("b", 10000)
    })

Aspettiamo che le goroutine finiscano

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

Eseguendo il programma vediamo che i contatori sono stati aggiornati come previsto.

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

Successivamente vedremo come implementare questo stesso compito di gestione dello stato usando solo goroutine e canali.

Prossimo esempio: .