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.
Défi
FacileConstruisons 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
Inventoryqui stocke les quantités de produits dans une map et utilise unsync.RWMutexpour 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éeAddStock(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. Renvoietrueen cas de réussite,falsesi le stock est insuffisant (utilisez un verrou exclusif)
N'oubliez pas d'utiliser
deferpour 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,getouremove), un nom de produit et, pour les opérationsadd/remove, une quantité.Pour chaque opération, affichez le résultat :
add: AffichezAdded [quantity] [product]get: Affichez[product]: [stock] in stockremove: AffichezRemoved [quantity] [product]en cas de réussite, ouInsufficient 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,getouremove) - Nom du produit
- Quantité (uniquement pour les opérations
addetremove)
- Type d'opération (
Par exemple, étant donné :
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesVotre sortie doit être :
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockLe 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é
}
}
}
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
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et modules GoPackages et importsNoms exportés et non exportésIntroduction à la POO en GoStructs comme classesDéfinir des méthodes sur des structsRécepteurs pointeurs ou par valeurInitialisation des structsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL’interface comme contratInterface vide (any)Assertion de typeCommutation de typeComposition d’interfacesInterfaces Stringer et ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter et SetterDissimulation de l’information en GoRécapitulatif – Fiches d’étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesSolution de contournement pour les méthodes génériquesRécapitulatif - Collection générique2Plongée approfondie dans les types et les structs
Types de base et compositesDéfinitions de types personnalisésTags de structStructs anonymesStructs imbriquésValeurs zéro et valeurs par défautRécapitulatif - Carnet de contacts5La composition plutôt que l’héritage
Pourquoi Go n’a pas d’héritageBases de l’inclusion de structsPromotion des méthodesInclure plusieurs structsInclusion ou agrégationMasquage des méthodes inclusesRécapitulatif : hiérarchie des employés8Gestion des erreurs et POO
L’interface errorTypes d’erreurs personnalisésEnrobage des erreurs (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - Analyseur de fichiers11Bibliothèque standard et POO
io.Reader et io.Writersort.InterfaceInterface fmt.Stringerencoding/json avec des structsInterface http.HandlerRécapitulatif - Modèles d’API REST14Modèles de conception – Partie 2
Modèle CommandeModèle AdaptateurModèle DécorateurModèle Méthode modèleModèle ÉtatModèle CompositeMiddleware comme décorateur3Pointeurs et mémoire
Notions de base des pointeurs en GoPointeurs vers des structuresPassage par valeur ou par référenceLa fonction new()Garbage collection en GoRécapitulatif - Constructeur de listes chaînées6Polymorphisme en Go
Polymorphisme via les interfacesDuck typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif – Processeur de paiements9Concurrence et POO
Bases des GoroutinesCanaux et communicationCanaux tamponnés ou non tamponnésInstruction selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures thread-safeRécapitulatif - Pool de workersEntraînez-vous par vous-même : Compilateur Go en ligne