Direct naar de hoofdinhoud
Module 7 · Les 4 van 534/50 in de cursus~14 min
Modulelessen (4/5)

`sync.Mutex` en `sync.WaitGroup`

Kanalen zijn het primaire idioom van Go, maar soms traditioneler primitieven zijn nodig. Het sync pakket biedt mutexen, wacht groepen en andere hulpprogramma's om de gedeelde staat te coördineren.

sync.Mutex: exclusief slot

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
}

Slechts één goroutine tegelijk komt binnen tussen Lock() en Unlock(). Verplicht patroon: defer mu.Unlock() direct na Lock().

sync.RWMutex: veel lezers, één schrijver

Voor een leeszware status:

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()

Alleen nuttig als leesbewerkingen echt domineren over schrijfbewerkingen; anders een normale sync.Mutex is eenvoudiger en vaak sneller dankzij de lagere interne gegevens stelling.

sync.WaitGroup: wacht op 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

Werkstroom:

  1. wg.Add(N) verhoogt de teller met N (voordat de goroutines worden gestart).
  2. Elke goroutine roept wg.Done() aan als deze klaar is (idealiter via defer).
  3. wg.Wait() blokkeert totdat de teller teruggaat naar 0.

sync.Once: thread-safe luie initialisatie

Go
var (
    once   sync.Once
    config *Config
)

func GetConfig() *Config {
    once.Do(func() {
        config = loadConfig()
    })
    return config
}

De callback in once.Do wordt precies één keer uitgevoerd, zelfs onder gelijktijdig gebruik oproepen. Singleton / lui initialisatiepatroon.

Mutex versus kanaal: wanneer wat te gebruiken

Richtlijnen (ook uit de officiële Go FAQ):

GevalVoorkeurstool
Eigendom van gegevens doorgevenkanaal
Werk verdelen (werkwachtrij)kanaal
Coördineren van onafhankelijke goroutineskanaal
Een struct-veld beveiligenMutex
Cache / tellerMutex of atomair
Singleton-referentiesync.Once

"Kanalen om te orkestreren, mutexen voor gedeelde gegevens" is een goede vuistregel.

Rasdetector

Voer tests uit met -race om dataraces te spotten:

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

De racedetector meet het binaire getal en registreert elke niet-gesynchroniseerde toegang tot gedeeld geheugen. Het is tool #1 om gelijktijdige code te valideren vóór productie.

Probeer het

Oefening#go.m7.l4.e1
Pogingen: 0Laden…

Bescherm de toename van het aantal met mu.Lock() + stel mu.Unlock() uit.

Editor laden…
Toon hint

stel mu.Unlock() uit direct nadat Lock() vrijgave garandeert, zelfs bij paniek.

Oplossing beschikbaar na 3 pogingen

Oefening#go.m7.l4.e2
Pogingen: 0Laden…

Start 3 goroutines en wacht op ze allemaal met behulp van sync.WaitGroup (Add(3), Done() in elke goroutine, Wait()).

Editor laden…
Toon hint

Voeg toe VOORDAT u de goroutine start; Gedaan BINNEN de goroutine met uitstel.

Oplossing beschikbaar na 3 pogingen

Quiz#go.m7.l4.e3
Klaar

Wat is het aanbevolen patroon om te garanderen dat de mutex wordt vrijgegeven?

Go
mu.Lock()
// ?
Antwoordmogelijkheden

Samenvatten

  • sync.Mutex beschermt de gedeelde status; altijd defer mu.Unlock().
  • Gebruik een pointer-ontvanger om te voorkomen dat de mutex wordt gekopieerd.
  • sync.RWMutex voor patronen met veel lezers/één schrijver (alleen als dit helpt).
  • sync.WaitGroup: Add vóór go, Done met defer, Wait om te wachten.
  • sync.Once: thread-safe luie initialisatie.
  • "Kanalen om te orkestreren, mutexen voor gedeelde gegevens".
  • go test -race: tool #1 om dataraces te spotten.