Menu
Coddy logo textTech

sync.Mutex & sync.RWMutex

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 63 von 107.

Während Kanäle Gos bevorzugte Methode zur Koordination von Goroutinen sind, musst du manchmal gemeinsam genutzte Daten direkt schützen. Das Paket sync stellt Mutexe bereit: Sperren, die sicherstellen, dass jeweils nur eine Goroutine 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, blockieren, 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 auch dann freigegeben wird, wenn die Funktion vorzeitig zurückkehrt oder eine Panik auslöst: ein gängiges und empfohlenes Muster.

Wenn Lesevorgänge häufig, Schreibvorgänge jedoch selten sind, bietet sync.RWMutex eine bessere Leistung. Es ermöglicht mehreren Lesern den gleichzeitigen Zugriff, 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
}

Verwende RLock()/RUnlock() für Leseoperationen und Lock()/Unlock() für Schreibvorgänge. Dadurch werden gleichzeitige Lesevorgänge ermöglicht und gleichzeitig sichere Schreibvorgänge gewährleistet.

challenge icon

Aufgabe

Einfach

Erstellen wir ein threadsicheres Inventarsystem, das die Lagerbestände von Produkten verfolgt. Dein System verarbeitet gleichzeitige Lese- und Schreibzugriffe sicher mithilfe von Mutexen und gewährleistet die Datenintegrität, wenn mehrere Vorgänge gleichzeitig stattfinden.

Du organisierst deinen Code auf zwei Dateien verteilt:

  • inventory.go: Definiere dein threadsicheres Bestandsverwaltungssystem.

    Erstelle eine Inventory-Struktur, die Produktmengen in einer Map speichert und den Zugriff mithilfe eines sync.RWMutex schützt. Dein Inventar soll die folgenden Vorgänge 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 (verwende eine exklusive Sperre, da dadurch Daten geändert werden)
    • GetStock(product string) int – Gibt den aktuellen Bestand eines Produkts zurück oder 0, falls es nicht gefunden wurde (verwende eine Lesesperre, da hierbei nur Daten gelesen werden)
    • RemoveStock(product string, quantity int) bool – Entfernt die Menge aus dem Bestand, wenn genügend Bestand vorhanden ist. Gibt true zurück, wenn der Vorgang erfolgreich war, und false, wenn der Bestand nicht ausreicht (verwende eine exklusive Sperre)

    Denke daran, defer zum Entsperren zu verwenden, damit die Sperren immer ordnungsgemäß freigegeben werden.

  • main.go: Lies die Vorgänge ein und demonstriere dein threadsicheres Inventar.

    Lies die Anzahl der Vorgänge ein und verarbeite anschließend jeden Vorgang. Jeder Vorgang besitzt einen Typ (add, get oder remove), einen Produktnamen und bei add- bzw. remove-Vorgängen eine Menge.

    Gib für jeden Vorgang das Ergebnis aus:

    • add: Gib Added [quantity] [product] aus
    • get: Gib [product]: [stock] in stock aus
    • remove: Gib bei Erfolg Removed [quantity] [product] aus, andernfalls Insufficient stock for [product]

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Anzahl der Vorgänge (Ganzzahl)
  • Folgende Zeilen: Für jeden Vorgang:
    • Vorgangstyp (add, get oder remove)
    • Produktname
    • Menge (nur für add- und remove-Vorgänge)

Beispielhaft wird Folgendes eingegeben:

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

Deine Ausgabe sollte wie folgt aussehen:

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

Der RWMutex ermöglicht es mehreren GetStock-Aufrufen, gleichzeitig zu lesen, während AddStock und RemoveStock beim Ändern des Inventars exklusiven Zugriff erhalten.

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))
	
	// 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 Inventar-Methode aufrufen
		// Das Ergebnis gemäß der Challenge-Beschreibung 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

Übe selbstständig: Online-Go-Compiler