sync.Mutex & sync.RWMutex
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 63 von 107.
Während Channels der bevorzugte Weg in Go sind, um Goroutinen zu koordinieren, müssen Sie manchmal gemeinsam genutzte Daten direkt schützen. Das sync-Paket bietet Mutexes – Sperren, die sicherstellen, dass jeweils nur eine Goroutine gleichzeitig auf eine Ressource zugreift.
Ein sync.Mutex verfügt über zwei Methoden: Lock() und Unlock(). Wenn eine Goroutine Lock() aufruft, erhält sie exklusiven Zugriff. Andere Goroutinen, die Lock() aufrufen, werden blockiert, bis Unlock() aufgerufen wird:
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
}Die Verwendung von defer c.mu.Unlock() stellt sicher, dass die Sperre aufgehoben wird, selbst wenn die Funktion vorzeitig beendet wird oder ein Panic auftritt – ein gängiges und empfohlenes Muster.
Wenn Lesezugriffe häufig, Schreibzugriffe jedoch selten sind, bietet sync.RWMutex eine bessere Performance. Es ermöglicht mehrere gleichzeitige Leser, aber Schreiber erhalten exklusiven Zugriff:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // Mehrere Leser erlaubt
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // Exklusiver Zugriff zum Schreiben
defer c.mu.Unlock()
c.data[key] = value
}Verwenden Sie RLock()/RUnlock() für Leseoperationen und Lock()/Unlock() für Schreibvorgänge. Dies ermöglicht gleichzeitige Lesevorgänge, während sichergestellt wird, dass Schreibvorgänge sicher sind.
Aufgabe
EinfachLassen Sie uns ein threadsicheres Inventarsystem erstellen, das die Lagerbestände von Produkten verfolgt. Ihr System wird gleichzeitige Lese- und Schreibvorgänge mithilfe von Mutexes sicher verarbeiten und so die Datenintegrität gewährleisten, wenn mehrere Operationen gleichzeitig stattfinden.
Sie werden Ihren Code auf zwei Dateien aufteilen:
inventory.go: Definieren Sie Ihr threadsicheres Inventarverwaltungssystem.Erstellen Sie ein
Inventory-Struct, das Produktmengen in einer Map speichert und einsync.RWMutexverwendet, um den Zugriff zu schützen. Ihr Inventar sollte diese Operationen unterstützen:NewInventory() *Inventory- Erstellt ein neues Inventar mit einer initialisierten MapAddStock(product string, quantity int)- Fügt die Menge zum Bestand eines Produkts hinzu (verwenden Sie eine exklusive Sperre, da dies Daten ändert)GetStock(product string) int- Gibt den aktuellen Bestand für ein Produkt zurück oder 0, falls nicht gefunden (verwenden Sie eine Lesesperre, da dies nur Daten liest)RemoveStock(product string, quantity int) bool- Entfernt die Menge aus dem Bestand, wenn genügend Bestand vorhanden ist. Gibttruebei Erfolg zurück,falsebei unzureichendem Bestand (verwenden Sie eine exklusive Sperre)
Denken Sie daran,
deferzum Entsperren zu verwenden, um sicherzustellen, dass Sperren immer ordnungsgemäß freigegeben werden.main.go: Lesen Sie Operationen ein und demonstrieren Sie Ihr threadsicheres Inventar.Lesen Sie die Anzahl der Operationen ein und verarbeiten Sie dann jede Operation. Jede Operation hat einen Typ (
add,getoderremove), einen Produktnamen und füradd/remove-Operationen eine Menge.Geben Sie für jede Operation das Ergebnis aus:
add: Geben SieAdded [quantity] [product]ausget: Geben Sie[product]: [stock] in stockausremove: Geben SieRemoved [quantity] [product]bei Erfolg aus, oderInsufficient stock for [product]falls nicht
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Anzahl der Operationen (Ganzzahl)
- Folgende Zeilen: Für jede Operation:
- Operationstyp (
add,getoderremove) - Produktname
- Menge (nur für
addundremoveOperationen)
- Operationstyp (
Zum Beispiel, gegeben:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesIhre Ausgabe sollte sein:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockDas RWMutex ermöglicht es mehreren GetStock-Aufrufen, gleichzeitig zu lesen, während AddStock und RemoveStock exklusiven Zugriff erhalten, wenn sie das Inventar ändern.
Spickzettel
Das sync-Paket bietet Mutexes, um gemeinsam genutzte Daten zu schützen, wenn mehrere Goroutinen direkten Zugriff auf dieselbe Ressource benötigen.
sync.Mutex
Ein sync.Mutex bietet exklusiven Zugriff mittels Lock() und 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
}Die Verwendung von defer mit Unlock() stellt sicher, dass die Sperre aufgehoben wird, selbst wenn die Funktion vorzeitig beendet wird oder ein Panic auftritt.
sync.RWMutex
Ein sync.RWMutex erlaubt mehrere gleichzeitige Leser, aber exklusiven Zugriff für Schreiber:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // Mehrere Leser erlaubt
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // Exklusiver Zugriff zum Schreiben
defer c.mu.Unlock()
c.data[key] = value
}Verwenden Sie RLock()/RUnlock() für Leseoperationen und Lock()/Unlock() für Schreiboperationen.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Anzahl der Operationen lesen
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Ein neues Inventar erstellen
inventory := NewInventory()
// Jede Operation verarbeiten
for i := 0; i < numOps; i++ {
// Operationstyp lesen
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
// Produktnamen lesen
productLine, _ := reader.ReadString('\n')
product := strings.TrimSpace(productLine)
// TODO: Jeden Operationstyp behandeln (add, get, remove)
// Für "add" und "remove" die Menge aus der Eingabe lesen
// Die entsprechende Inventarmethode aufrufen
// Das Ergebnis gemäß der Aufgabenbeschreibung ausgeben
switch opType {
case "add":
// TODO: Menge lesen, Bestand hinzufügen, Ergebnis ausgeben
case "get":
// TODO: Bestand abrufen, Ergebnis ausgeben
case "remove":
// TODO: Menge lesen, Bestand entfernen, entsprechendes Ergebnis ausgeben
}
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool