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.

Bien que les canaux soient le moyen privilégié de Go pour coordonner les goroutines, vous devez parfois protéger directement des données partagées. Le package sync fournit des mutex : 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 qui appellent 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
}

Utiliser defer c.mu.Unlock() garantit que le verrou est libéré même si la fonction se termine prématurément ou provoque une 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 d’accéder simultanément aux données, 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 la sécurité des écritures.

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 en toute sécurité les lectures et écritures concurrentes à 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 les accès. Votre inventaire doit prendre en charge les opérations suivantes :

    • 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 puisque cette opération modifie les données)
    • GetStock(product string) int - Renvoie le stock actuel d'un produit, ou 0 si celui-ci n'est pas trouvé (utilisez un verrou de lecture puisque cette opération ne fait que lire les données)
    • RemoveStock(product string, quantity int) bool - Retire une quantité du stock si le stock disponible est suffisant. Renvoie true en cas de réussite, false si le stock est insuffisant (utilisez un verrou exclusif)

    N'oubliez pas d'utiliser defer pour déverrouiller et vous assurer que les verrous sont toujours correctement libérés.

  • main.go : Lisez les opérations et démontrez le fonctionnement de votre inventaire thread-safe.

    Lisez le nombre d'opérations, puis traitez chaque opération. Chaque opération possède 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 réussite, ou Insufficient stock for [product] dans le cas contraire

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 doit ê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 lorsqu'ils modifient l'inventaire.

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 challenge
		
		switch opType {
		case "add":
			// TODO: Lire la quantité, ajouter le stock, afficher le résultat
			
		case "get":
			// TODO: Obtenir le stock, afficher le résultat
			
		case "remove":
			// TODO: Lire la quantité, retirer le 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

Entraînez-vous par vous-même : Compilateur Go en ligne