Introduction
Ce laboratoire vise à démontrer comment utiliser des mutex pour accéder en toute sécurité à des données à travers plusieurs goroutines.
Mutexes
Le problème à résoudre dans ce laboratoire est d'incrémenter un compteur nommé dans une boucle en utilisant plusieurs goroutines, et de s'assurer que l'accès au compteur est synchronisé.
- Utiliser une structure
Containerpour stocker une carte de compteurs. - Utiliser un
Mutexpour synchroniser l'accès à la cartecounters. - La structure
Containerdevrait avoir une méthodeincqui prend une chaîne de caractèresnameet incrémente le compteur correspondant dans la cartecounters. - La méthode
incdevrait verrouiller le mutex avant d'accéder à la cartecounters, et le déverrouiller à la fin de la fonction en utilisant une instructiondefer. - Utiliser la structure
sync.WaitGrouppour attendre que les goroutines se terminent. - Utiliser la fonction
fmt.Printlnpour imprimer la cartecounters.
## Exécution du programme montre que les compteurs
## sont mis à jour comme prévu.
## Ensuite, nous allons voir comment implémenter cette même tâche
## de gestion d'état en utilisant seulement des goroutines et des canaux.
Voici le code complet ci-dessous :
// Dans l'exemple précédent, nous avons vu comment gérer un état
// de compteur simple en utilisant [opérations atomiques](atomic-counters).
// Pour un état plus complexe, nous pouvons utiliser un [_mutex_](https://en.wikipedia.org/wiki/Mutual_exclusion)
// pour accéder en toute sécurité à des données à travers plusieurs goroutines.
package main
import (
"fmt"
"sync"
)
// Container stocke une carte de compteurs ; comme nous voulons
// la mettre à jour de manière concurrente à partir de plusieurs goroutines,
// nous ajoutons un `Mutex` pour synchroniser l'accès.
// Notez que les mutexes ne doivent pas être copiés, donc si cette
// `struct` est passée en boucle, cela devrait être fait par
// pointeur.
type Container struct {
mu sync.Mutex
counters map[string]int
}
func (c *Container) inc(name string) {
// Verrouillez le mutex avant d'accéder à `counters` ; déverrouillez
// le à la fin de la fonction en utilisant une instruction [defer](defer).
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}
func main() {
c := Container{
// Notez que la valeur zéro d'un mutex est utilisable telle quelle, donc
// aucune initialisation n'est requise ici.
counters: map[string]int{"a": 0, "b": 0},
}
var wg sync.WaitGroup
// Cette fonction incrémente un compteur nommé
// dans une boucle.
doIncrement := func(name string, n int) {
for i := 0; i < n; i++ {
c.inc(name)
}
wg.Done()
}
// Exécutez plusieurs goroutines en parallèle ; notez
// qu'elles accèdent toutes au même `Container`,
// et deux d'entre elles accèdent au même compteur.
wg.Add(3)
go doIncrement("a", 10000)
go doIncrement("a", 10000)
go doIncrement("b", 10000)
// Attendez que les goroutines se terminent
wg.Wait()
fmt.Println(c.counters)
}
Résumé
Dans ce laboratoire, nous avons appris comment utiliser des mutex pour accéder en toute sécurité à des données à travers plusieurs goroutines. Nous avons créé une structure Container pour stocker une carte de compteurs, et utilisé un Mutex pour synchroniser l'accès à la carte counters. Nous avons également implémenté une méthode inc pour incrémenter le compteur nommé, et utilisé la structure sync.WaitGroup pour attendre que les goroutines se terminent. Enfin, nous avons imprimé la carte counters en utilisant la fonction fmt.Println.