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.
Défi
FacileConstruisons 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
Inventoryqui stocke les quantités de produits dans une map et utilise unsync.RWMutexpour 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éeAddStock(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. Renvoietrueen cas de succès,falsesi le stock est insuffisant (utilisez un verrou exclusif)
N'oubliez pas d'utiliser
deferpour 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, ouremove), 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 succès, ouInsufficient 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, ouremove) - 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 devrait ê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 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é
}
}
}
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 vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool