Lekcje modułu (4/5)
`sync.Mutex` i `sync.WaitGroup`
Kanały to podstawowy idiom Go, ale czasami bardziej tradycyjny
potrzebne są prymitywy. Pakiet sync udostępnia muteksy, czekaj
grupy i inne narzędzia do koordynowania stanu współdzielonego.
sync.Mutex: blokada na wyłączność
import "sync"
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}Tylko jedna goroutine na raz wchodzi pomiędzy Lock() i Unlock().
Obowiązkowy wzór: defer mu.Unlock() zaraz po Lock().
sync.RWMutex: wielu czytelników, jeden autor
Dla stanu z dużą ilością odczytu:
var mu sync.RWMutex
mu.RLock() // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()
mu.Lock() // write lock: esclusivo
data = newValue
mu.Unlock()Przydatne tylko wtedy, gdy odczyty rzeczywiście dominują nad zapisami; w przeciwnym razie regularnie
sync.Mutex jest prostszy i często szybszy dzięki niższemu wewnętrznemu
spór.
sync.WaitGroup: czekaj na N goroutines
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
work(id)
}(i)
}
wg.Wait() // blocca finché tutti i Done() sono arrivatiPrzebieg pracy:
wg.Add(N)zwiększa licznik o N (przed uruchomieniem goroutines).- Każda goroutine po zakończeniu wywołuje
wg.Done()(najlepiej poprzezdefer). wg.Wait()blokuje, aż licznik powróci do 0.
sync.Once: leniwa inicjalizacja bezpieczna dla wątków
var (
once sync.Once
config *Config
)
func GetConfig() *Config {
once.Do(func() {
config = loadConfig()
})
return config
}Wywołanie zwrotne w once.Do działa dokładnie raz, nawet w trybie współbieżnym
dzwoni. Singleton/leniwy wzorzec inicjalizacji.
Mutex kontra kanał: kiedy czego użyć
Wytyczne (również z oficjalnego FAQ Go):
| Sprawa | Preferowane narzędzie |
|---|---|
| Przekazanie własności danych | kanał |
| Dystrybucja pracy (kolejka pracy) | kanał |
| Koordynowanie niezależnych goroutines | kanał |
| Ochrona pola struktury | Muteks |
| Pamięć podręczna / licznik | Mutex lub atomowy |
| Odniesienie do Singletona | KODEF0 |
„Kanały do orkiestracji, muteksy dla współdzielonych danych” to dobra praktyczna zasada.
Detektor wyścigów
Uruchom testy z -race, aby wykryć wyścigi danych:
go test -race ./...
go run -race main.goDetektor wyścigu analizuje dane binarne i rejestruje każdą niezsynchronizowaną sytuację dostęp do pamięci współdzielonej. Jest to narzędzie nr 1 do sprawdzania poprawności kodu współbieżnego przed produkcją.
Spróbuj
Chroń przyrost licznika za pomocą mu.Lock() + odroczenie mu.Unlock().
Pokaż wskazówkę
odłóż mu.Unlock() zaraz po Lock() gwarantuje zwolnienie nawet w przypadku paniki.
Rozwiązanie dostępne po 3 próbach
Uruchom 3 goroutines i poczekaj na wszystkie, używając sync.WaitGroup (Add(3), Done() w każdym goroutine, Wait()).
Pokaż wskazówkę
Dodaj PRZED uruchomieniem goroutine; Wykonano WEWNĄTRZ goroutine z odroczeniem.
Rozwiązanie dostępne po 3 próbach
Jaki jest zalecany wzorzec gwarantujący zwolnienie muteksu?
mu.Lock()
// ?Podsumowanie
sync.Mutexchroni stan współdzielony; zawsze KODEF1.- Użyj odbiornika wskaźnika, aby uniknąć kopiowania muteksu.
sync.RWMutexdla wzorców wielu czytelników/jednego autora (tylko jeśli to pomaga).sync.WaitGroup:Addprzedgo,Donezdefer,Waitczekać.sync.Once: bezpieczna dla wątków, leniwa inicjalizacja.- „Kanały do orkiestracji, muteksy dla współdzielonych danych”.
defer mu.Unlock()0: narzędzie nr 1 do wykrywania wyścigów danych.