Przejdź do głównej treści
eLearner.app
Moduł 7 · Lekcja 4 z 534/50 w kursie~14 min
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ść

Go
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:

Go
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

Go
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 arrivati

Przebieg pracy:

  1. wg.Add(N) zwiększa licznik o N (przed uruchomieniem goroutines).
  2. Każda goroutine po zakończeniu wywołuje wg.Done() (najlepiej poprzez defer).
  3. wg.Wait() blokuje, aż licznik powróci do 0.

sync.Once: leniwa inicjalizacja bezpieczna dla wątków

Go
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):

SprawaPreferowane narzędzie
Przekazanie własności danychkanał
Dystrybucja pracy (kolejka pracy)kanał
Koordynowanie niezależnych goroutineskanał
Ochrona pola strukturyMuteks
Pamięć podręczna / licznikMutex lub atomowy
Odniesienie do SingletonaKODEF0

„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:

Bash
go test -race ./...
go run -race main.go

Detektor 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

Ćwiczenie#go.m7.l4.e1
Próby: 0Ładowanie...

Chroń przyrost licznika za pomocą mu.Lock() + odroczenie mu.Unlock().

Ładowanie edytora...
Pokaż wskazówkę

odłóż mu.Unlock() zaraz po Lock() gwarantuje zwolnienie nawet w przypadku paniki.

Rozwiązanie dostępne po 3 próbach

Ćwiczenie#go.m7.l4.e2
Próby: 0Ładowanie...

Uruchom 3 goroutines i poczekaj na wszystkie, używając sync.WaitGroup (Add(3), Done() w każdym goroutine, Wait()).

Ładowanie edytora...
Pokaż wskazówkę

Dodaj PRZED uruchomieniem goroutine; Wykonano WEWNĄTRZ goroutine z odroczeniem.

Rozwiązanie dostępne po 3 próbach

Quiz#go.m7.l4.e3
Gotowe

Jaki jest zalecany wzorzec gwarantujący zwolnienie muteksu?

Go
mu.Lock()
// ?
Opcje odpowiedzi

Podsumowanie

  • sync.Mutex chroni stan współdzielony; zawsze KODEF1.
  • Użyj odbiornika wskaźnika, aby uniknąć kopiowania muteksu.
  • sync.RWMutex dla wzorców wielu czytelników/jednego autora (tylko jeśli to pomaga).
  • sync.WaitGroup: Add przed go, Done z defer, Wait czekać.
  • 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.