Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 ein sync.RWMutex verwendet, um den Zugriff zu schützen. Ihr Inventar sollte diese Operationen unterstützen:

    • NewInventory() *Inventory - Erstellt ein neues Inventar mit einer initialisierten Map
    • AddStock(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. Gibt true bei Erfolg zurück, false bei unzureichendem Bestand (verwenden Sie eine exklusive Sperre)

    Denken Sie daran, defer zum 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, get oder remove), einen Produktnamen und für add/remove-Operationen eine Menge.

    Geben Sie für jede Operation das Ergebnis aus:

    • add: Geben Sie Added [quantity] [product] aus
    • get: Geben Sie [product]: [stock] in stock aus
    • remove: Geben Sie Removed [quantity] [product] bei Erfolg aus, oder Insufficient stock for [product] falls nicht

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Anzahl der Operationen (Ganzzahl)
  • Folgende Zeilen: Für jede Operation:
    • Operationstyp (add, get oder remove)
    • Produktname
    • Menge (nur für add und remove Operationen)

Zum Beispiel, gegeben:

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

Ihre Ausgabe sollte sein:

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

Das 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
			
		}
	}
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung