W Go do synchronizacji konkurencyjnych gorutin używa się struktur z pakietu sync, najczęściej są to sync.Mutex, sync.RWMutex i sync.WaitGroup.
sync.Mutex zapewnia mechanizmy wzajemnego wykluczenia przy dostępie do współdzielonych danych. Jego metody to Lock() (blokuje) i Unlock() (odblokowuje).
sync.RWMutex rozszerza możliwości zwykłego mutexa: pozwala na równoległe czytanie, ale wyłączną zmianę.
sync.WaitGroup służy do oczekiwania na zakończenie grupy gorutin. Używając Add(int), Done() i Wait(), zarządzasz cyklem życia pracy równoległej.
Na przykład:
var mu sync.RWMutex data := 0 // Czytanie mu.RLock() fmt.Println(data) mu.RUnlock() // Zapis mu.Lock() data = 42 mu.Unlock()
Szczegóły:
Unlock w bloku defer, aby nie zapomnieć odblokować mutexa nawet w przypadku paniki.WaitGroup.Done() odpowiada Add().Czy można zablokować ten sam sync.Mutex (lub RWMutex) dwa razy pod rząd w tej samej gorutinie? Co się wydarzy?
Odpowiedź: Nie, jeśli wywołasz Lock() na tym samym Mutex dwa razy pod rząd bez pośredniego Unlock(), gorutyna zablokuje się na zawsze (deadlock). W Go mutexy nie są rekurencyjne.
Przykład:
var mu sync.Mutex mu.Lock() // ... mu.Lock() // DEADLOCK: zablokujemy się na zawsze, ponieważ ten sam wątek już trzyma blokadę
Historia
W projekcie dla high-load REST API programista owinął całe przetwarzanie żądania jednym mutexem. Spowodowało to gwałtowny spadek wydajności — tylko jedno żądanie mogło być przetwarzane jednocześnie, chociaż planowane było obsługiwanie tysięcy klientów. Powód — brak wiedzy na temat różnicy między Mutex a RWMutex oraz ignorowanie równoległych odczytów.
Historia
Podczas kopiowania struktury z Mutex w środku jeden z członków zespołu przypadkowo przekazał kopię do innej funkcji. Doprowadziło to do panicznego komunikatu "sync: copy of sync.Mutex" i awarii w produkcji pod wysokim obciążeniem.
Historia
Podczas używania WaitGroup zapomniano wywołać Done() w kilku gorutinach, co doprowadziło do wiecznego oczekiwania Wait(), blokującego główny wątek. W rezultacie usług nie można było używać do ręcznego ponownego uruchomienia.