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.
Wyzwanie
ŁatwyZbudujmy 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żywasync.RWMutexdo 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. Zwracatrue, jeśli operacja się powiedzie, lubfalse, jeśli zapasy są niewystarczające (użyj blokady wyłącznej)
Pamiętaj, aby używać
deferdo 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,getlubremove), nazwę produktu oraz ilość w przypadku operacjiadd/remove.Dla każdej operacji wypisz wynik:
add: WypiszAdded [quantity] [product]get: Wypisz[product]: [stock] in stockremove: WypiszRemoved [quantity] [product], jeśli operacja się powiedzie, lubInsufficient 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,getlubremove) - Nazwa produktu
- Ilość (tylko dla operacji
addiremove)
- Typ operacji (
Na przykład, dla danych wejściowych:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesWynik powinien być następujący:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockRWMutex 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
}
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online