Menu
Coddy logo textTech

sync.Mutex et sync.RWMutex

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 63 sur 107.

Alors que les channels sont le moyen privilégié de Go pour coordonner les goroutines, vous avez parfois besoin de protéger directement les données partagées. Le package sync fournit des mutexes — des verrous qui garantissent qu'une seule goroutine accède à une ressource à la fois.

Un sync.Mutex possède deux méthodes : Lock() et Unlock(). Lorsqu'une goroutine appelle Lock(), elle obtient un accès exclusif. Les autres goroutines appelant Lock() seront bloquées jusqu'à ce que Unlock() soit appelée :

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

L'utilisation de defer c.mu.Unlock() garantit que le verrou est libéré même si la fonction se termine prématurément ou panique — un modèle courant et recommandé.

Lorsque les lectures sont fréquentes mais que les écritures sont rares, sync.RWMutex offre de meilleures performances. Il permet plusieurs lecteurs simultanément, mais les écrivains bénéficient d'un accès exclusif :

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // plusieurs lecteurs autorisés
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // accès exclusif pour l'écriture
    defer c.mu.Unlock()
    c.data[key] = value
}

Utilisez RLock()/RUnlock() pour les opérations de lecture et Lock()/Unlock() pour les écritures. Cela permet des lectures simultanées tout en garantissant que les écritures sont sécurisées.

challenge icon

Défi

Facile

Construisons un système d'inventaire thread-safe qui suit les niveaux de stock des produits. Votre système gérera les lectures et écritures concurrentes en toute sécurité à l'aide de mutex, garantissant l'intégrité des données lorsque plusieurs opérations se produisent simultanément.

Vous organiserez votre code sur deux fichiers :

  • inventory.go : Définissez votre système de gestion d'inventaire thread-safe.

    Créez une structure Inventory qui stocke les quantités de produits dans une map et utilise un sync.RWMutex pour protéger l'accès. Votre inventaire doit prendre en charge ces opérations :

    • NewInventory() *Inventory - Crée un nouvel inventaire avec une map initialisée
    • AddStock(product string, quantity int) - Ajoute une quantité au stock d'un produit (utilisez un verrou exclusif car cela modifie les données)
    • GetStock(product string) int - Renvoie le stock actuel pour un produit, ou 0 s'il n'est pas trouvé (utilisez un verrou de lecture car cela ne fait que lire les données)
    • RemoveStock(product string, quantity int) bool - Retire une quantité du stock si le stock est suffisant. Renvoie true en cas de succès, false si le stock est insuffisant (utilisez un verrou exclusif)

    N'oubliez pas d'utiliser defer pour le déverrouillage afin de garantir que les verrous sont toujours libérés correctement.

  • main.go : Lit les opérations et fait la démonstration de votre inventaire thread-safe.

    Lisez le nombre d'opérations, puis traitez chaque opération. Chaque opération a un type (add, get, ou remove), un nom de produit et, pour les opérations add/remove, une quantité.

    Pour chaque opération, affichez le résultat :

    • add : Affichez Added [quantity] [product]
    • get : Affichez [product]: [stock] in stock
    • remove : Affichez Removed [quantity] [product] en cas de succès, ou Insufficient stock for [product] sinon

Les entrées suivantes seront fournies :

  • Ligne 1 : Nombre d'opérations (entier)
  • Lignes suivantes : Pour chaque opération :
    • Type d'opération (add, get, ou remove)
    • Nom du produit
    • Quantité (uniquement pour les opérations add et remove)

Par exemple, étant donné :

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

Votre sortie devrait être :

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

Le RWMutex permet à plusieurs appels GetStock de lire simultanément, tandis que AddStock et RemoveStock obtiennent un accès exclusif lors de la modification de l'inventaire.

Aide-mémoire

Le package sync fournit des mutexes pour protéger les données partagées lorsque plusieurs goroutines ont besoin d'un accès direct à la même ressource.

sync.Mutex

Un sync.Mutex fournit un accès exclusif en utilisant Lock() et Unlock() :

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

L'utilisation de defer avec Unlock() garantit que le verrou est libéré même si la fonction se termine prématurément ou panique.

sync.RWMutex

Un sync.RWMutex permet plusieurs lecteurs simultanés mais un accès exclusif pour les rédacteurs :

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // plusieurs lecteurs autorisés
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // accès exclusif pour l'écriture
    defer c.mu.Unlock()
    c.data[key] = value
}

Utilisez RLock()/RUnlock() pour les opérations de lecture et Lock()/Unlock() pour les opérations d'écriture.

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lire le nombre d'opérations
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Créer un nouvel inventaire
	inventory := NewInventory()
	
	// Traiter chaque opération
	for i := 0; i < numOps; i++ {
		// Lire le type d'opération
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// Lire le nom du produit
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: Gérer chaque type d'opération (add, get, remove)
		// Pour "add" et "remove", lire la quantité depuis l'entrée
		// Appeler la méthode d'inventaire appropriée
		// Afficher le résultat selon la description du défi
		
		switch opType {
		case "add":
			// TODO: Lire la quantité, ajouter au stock, afficher le résultat
			
		case "get":
			// TODO: Obtenir le stock, afficher le résultat
			
		case "remove":
			// TODO: Lire la quantité, retirer du stock, afficher le résultat approprié
			
		}
	}
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet