Mutexes

Mutexes #

// Dans l'exemple précédent nous avons vu comment gérer des compteurs d'état simples avec des opérations atomiques. Pour des états plus compliqués, on peut utiliser un _[mutex](http://en.wikipedia.org/wiki/Mutual_exclusion)_ pour accéder de  manière sûre à des données à travers plusieurs goroutines.

package main

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

func main() {

    // Pour notre exemple, l'état `state` sera une map.
    var state = make(map[int]int)

    // Ce `mutex` va synchroniser l'accès à `state`.
    var mutex = &sync.Mutex{}

    // Pour comparer l'approche avec des mutexes avec une autre que nous verrons plus tard, `ops` va compter combien d'opérations nous réalisons avec l'état.
    var ops int64 = 0

    // Ici on lance 100 goroutines pour exécuter des lectures répétées sur l'état.
    for r := 0; r < 100; r++ {
        go func() {
            total := 0
            for {

                // A chaque lecture, on sélectionne une
                // clé à laquelle on souhaite accéder,
                // on bloque le mutex avec `Lock()` pour
                // s'assurer un accès exclusif à l'état,
                // on lit la valeur de la clé choisie, on
                // débloque le mutex, puis on incrémente
                // le compteur.
                key := rand.Intn(5)
                mutex.Lock()
                total += state[key]
                mutex.Unlock()
                atomic.AddInt64(&ops, 1)

                // Pour nous assurer que cette goroutine
                // ne prend pas toutes les ressources, on
                // rend la main explicitement après
                // chaque opération avec
                // `runtime.Gosched()`. Le programmateur
                // gère normalement automatiquement ceci,
                // par ex. après les opérations sur les
                // canaux et pour les appels bloquants
                // comme `time.Sleep`, mais dans ce cas
                // on doit le faire manuellement.
                runtime.Gosched()
            }
        }()
    }

    // On démarre également 10 goroutines pour simuler
    // des écritures, de la même manière que pour les
    // écritures.
    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)
                mutex.Lock()
                state[key] = val
                mutex.Unlock()
                atomic.AddInt64(&ops, 1)
                runtime.Gosched()
            }
        }()
    }

    // On fait travailler les 10 goroutines sur les
    // `state` et `mutex` pendant une seconde.
    time.Sleep(time.Second)

    // On rapporte le nombre total d'opérations
    // réalisées.
    opsFinal := atomic.LoadInt64(&ops)
    fmt.Println("ops:", opsFinal)

    // Avec un verrou final sur le mutex de `state`, on peut
    // connaitre l'état final.
    mutex.Lock()
    fmt.Println("state:", state)
    mutex.Unlock()
}
# Lancer  le programme montre qu'on a exécuté environ
# 3,500,000 d'opérations sur `state`, synchronisées par un
# `mutex`
$ go run mutexes.go
ops: 3598302
state: map[1:38 4:98 2:23 3:85 0:44]

# Ensuite nous verrons comment implémenter la même
# gestion d'état avec uniquement des goroutines et
# des canaux.