Mutex

Mutex #

// Nell'esempio precedente abbiamo visto come gestire
// semplici addizioni utilizzando delle operazioni
// atomiche. Per una gestione dello stato più complessa
// usiamo le _[mutex](https://it.wikipedia.org/wiki/Mutex)_
// per accedere con sicurezza a dati condivisi
// attraverso multiple goroutine.

package main

import (
    "fmt"
    "math/rand"
    "runtime"
    "sync"
    "sync/atomic"
    "time"
)

func main() {

    // Per il nostro esempio lo `stato` sarà una
    // mappa.
    var stato = make(map[int]int)

    // Questa `mutex` sincronizzerà l'accesso a `stato`.
    var mutex = &sync.Mutex{}

    // Per confrontare l'approccio basato sulle mutex
    // piuttosto che altri, `ops` conterà quante
    // operazioni effettuiamo su `stato`.
    var ops int64 = 0

    // Di seguito facciamo partire 100 goroutine che
    // dovranno eseguire ripetute operazioni di lettura
    // sullo stato.
    for r := 0; r < 100; r++ {
        go func() {
            totale := 0
            for {

                // Per ogni lettura scegliamo una chiave
                // per accedere alla map, facciamo un
                // `Lock()` sulla `mutex` per avere
                // controllo esclusivo sullo `stato`,
                // leggiamo il valore alla chiave
                // scelta, sblocchiamo (`Unlock()`) la
                // mutex, e incrementiamo il numero di
                // operazioni (`ops`).
                chiave := rand.Intn(5)
                mutex.Lock()
                totale += stato[chiave]
                mutex.Unlock()
                atomic.AddInt64(&ops, 1)

                // Per essere sicuri che questa goroutine
                // non blocchi lo scheduler, cediamo
                // esplicitamente il controllo dopo ogni
                // operazione utilizzando
                // `runtime.Gosched()`. In genere viene
                // eseguito automaticamente ad esempio
                // con le operazioni sui channel o con
                // funzioni come `time.Sleep`, ma in
                // questo caso abbiamo bisogno di farlo
                // manualmente.
                runtime.Gosched()
            }
        }()
    }

    // Faremo inoltre partire altre 10 goroutine per
    // simulare la scrittura, con lo stesso pattern
    // che abbiamo usato per la lettura.
    for w := 0; w < 10; w++ {
        go func() {
            for {
                chiave := rand.Intn(5)
                valore := rand.Intn(100)
                mutex.Lock()
                stato[chiave] = valore
                mutex.Unlock()
                atomic.AddInt64(&ops, 1)
                runtime.Gosched()
            }
        }()
    }

    // Lasciamo ora lavorare le 10 goroutine per un
    // secondo.
    time.Sleep(time.Second)

    // Prendiamo e stampiamo il numero di operazioni
    // finale.
    opsFinal := atomic.LoadInt64(&ops)
    fmt.Println("ops:", opsFinal)

    // Con un lock finale sullo `stato`, vediamo come
    // si è evoluto lo stato.
    mutex.Lock()
    fmt.Println("stato:", stato)
    mutex.Unlock()
}
# Eseguendo il nostro programma vedremo che abbiamo
# eseguito circa 4,000,000 di operazioni sul nostro
# `stato` sincronizzato da `mutex`.
$ go run mutexes.go
ops: 4272750
stato: map[4:29 3:50 0:39 1:71 2:51]

# Come prossima cosa, vedremo come come implementare
# questa gestione della memoria condivisa usando
# esclusivamente goroutine e channel.