Menu
Coddy logo textTech

sync.Mutex i sync.RWMutex

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 63 z 107.

Chociaż kanały są preferowanym przez Go sposobem koordynowania gorutyn, czasami trzeba bezpośrednio chronić współdzielone dane. Pakiet sync udostępnia muteksy: blokady, które gwarantują, że w danym momencie tylko jedna gorutyna uzyskuje dostęp do zasobu.

Element sync.Mutex ma dwie metody: Lock() i Unlock(). Gdy gorutyna wywołuje Lock(), uzyskuje wyłączny dostęp. Inne gorutyny wywołujące Lock() będą zablokowane do momentu wywołania 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
}

Użycie defer c.mu.Unlock() zapewnia zwolnienie blokady, nawet jeśli funkcja zakończy działanie wcześniej lub zgłosi panikę: to powszechna i zalecana praktyka.

Gdy odczyty są częste, a zapisy rzadkie, sync.RWMutex zapewnia lepszą wydajność. Umożliwia jednoczesny dostęp wielu czytelnikom, ale zapisujący uzyskują wyłączny dostęp:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}
func (c *Cache) Get(key string) string {
    c.mu.RLock()         // dozwolonych jest wielu czytających jednocześnie
    defer c.mu.RUnlock()
    return c.data[key]
}
func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // wyłączny dostęp do zapisu
    defer c.mu.Unlock()
    c.data[key] = value
}

Używaj RLock()/RUnlock() do operacji odczytu i Lock()/Unlock() do zapisów. Umożliwia to równoczesne odczyty, zapewniając jednocześnie bezpieczeństwo zapisów.

challenge icon

Wyzwanie

Łatwy

Zbudujmy bezpieczny współbieżnie system zarządzania zapasami, który śledzi poziomy zapasów produktów. System będzie bezpiecznie obsługiwać równoczesne odczyty i zapisy za pomocą mutexów, zapewniając integralność danych, gdy wiele operacji odbywa się jednocześnie.

Rozmieścisz kod w dwóch plikach:

  • inventory.go: Zdefiniuj bezpieczny współbieżnie system zarządzania zapasami.

    Utwórz strukturę Inventory, która przechowuje ilości produktów w mapie i używa sync.RWMutex do ochrony dostępu. System zarządzania zapasami powinien obsługiwać następujące operacje:

    • NewInventory() *Inventory - Tworzy nowy system zarządzania zapasami ze zainicjalizowaną mapą
    • AddStock(product string, quantity int) - Dodaje ilość do zapasów produktu (użyj blokady wyłącznej, ponieważ modyfikuje dane)
    • GetStock(product string) int - Zwraca bieżący stan zapasów produktu lub 0, jeśli produkt nie został znaleziony (użyj blokady do odczytu, ponieważ ta operacja tylko odczytuje dane)
    • RemoveStock(product string, quantity int) bool - Usuwa ilość z zapasów, jeśli jest ich wystarczająco dużo. Zwraca true, jeśli operacja się powiedzie, lub false, jeśli zapasy są niewystarczające (użyj blokady wyłącznej)

    Pamiętaj, aby używać defer do zwalniania blokady, aby mieć pewność, że blokady są zawsze prawidłowo zwalniane.

  • main.go: Odczytaj operacje i zaprezentuj działanie bezpiecznego współbieżnie systemu zarządzania zapasami.

    Odczytaj liczbę operacji, a następnie przetwórz każdą z nich. Każda operacja ma typ (add, get lub remove), nazwę produktu oraz ilość w przypadku operacji add/remove.

    Dla każdej operacji wypisz wynik:

    • add: Wypisz Added [quantity] [product]
    • get: Wypisz [product]: [stock] in stock
    • remove: Wypisz Removed [quantity] [product], jeśli operacja się powiedzie, lub Insufficient stock for [product], jeśli się nie powiedzie

Podane zostaną następujące dane wejściowe:

  • Wiersz 1: Liczba operacji (liczba całkowita)
  • Kolejne wiersze: Dla każdej operacji:
    • Typ operacji (add, get lub remove)
    • Nazwa produktu
    • Ilość (tylko dla operacji add i remove)

Na przykład, dla danych wejściowych:

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

Wynik powinien być następujący:

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

RWMutex umożliwia równoczesny odczyt przez wiele wywołań GetStock, natomiast AddStock i RemoveStock uzyskują wyłączny dostęp podczas modyfikowania zapasów.

Spróbuj swoich sił

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Wczytaj liczbę operacji
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Utwórz nowy magazyn
	inventory := NewInventory()
	
	// Przetwórz każdą operację
	for i := 0; i < numOps; i++ {
		// Wczytaj typ operacji
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// Wczytaj nazwę produktu
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: Obsłuż każdy typ operacji (add, get, remove)
		// Dla "add" i "remove" wczytaj ilość z wejścia
		// Wywołaj odpowiednią metodę magazynu
		// Wypisz wynik zgodnie z opisem zadania
		
		switch opType {
		case "add":
			// TODO: Wczytaj ilość, dodaj towar do magazynu, wypisz wynik
			
		case "get":
			// TODO: Pobierz stan magazynowy, wypisz wynik
			
		case "remove":
			// TODO: Wczytaj ilość, usuń towar z magazynu, wypisz odpowiedni wynik
			
		}
	}
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online