In Go, per sincronizzare le goroutine concorrenti, si utilizzano strutture del pacchetto sync, le più comuni sono sync.Mutex, sync.RWMutex e sync.WaitGroup.
sync.Mutex fornisce meccanismi di mutua esclusione nell'accesso ai dati condivisi. I suoi metodi sono Lock() (blocca) e Unlock() (sblocca).
sync.RWMutex estende le capacità di un normale mutex: consente la lettura parallela, ma modifica esclusiva.
sync.WaitGroup è destinato ad attendere il completamento di un gruppo di goroutine. Con Add(int), Done() e Wait() si gestisce il ciclo di vita del lavoro parallelo.
Ad esempio:
var mu sync.RWMutex data := 0 // Lettura mu.RLock() fmt.Println(data) mu.RUnlock() // Scrittura mu.Lock() data = 42 mu.Unlock()
Peculiarità:
Unlock nel blocco defer per non dimenticare di sbloccare il mutex anche in caso di panico.WaitGroup.Done() corrisponda a Add().È possibile acquisire lo stesso sync.Mutex (o RWMutex) due volte di seguito nella stessa goroutine? Cosa succederà?
Risposta: No, se chiami Lock() sullo stesso Mutex due volte di seguito senza un Unlock() intermedio, la goroutine si bloccherà per sempre (deadlock). In Go, i mutex non sono ricorsivi.
Esempio:
var mu sync.Mutex mu.Lock() // ... mu.Lock() // DEADLOCK: ci blocchiamo per sempre, poiché lo stesso thread ha già il lock
Storia
In un progetto per un'API REST ad alto carico, uno sviluppatore ha avvolto l'intera elaborazione della richiesta con un mutex. Questo ha causato un calo drastico delle prestazioni: solo una richiesta poteva essere elaborata alla volta, mentre si prevedeva di servire migliaia di clienti. La causa è stata la mancanza di conoscenza della differenza tra Mutex e RWMutex e l'ignoranza delle letture parallele.
Storia
Durante la copia di una struttura con Mutex all'interno, uno dei membri del team ha accidentalmente passato una copia a un'altra funzione. Questo ha portato a un messaggio di panico "sync: copy of sync.Mutex" e a crash in produzione sotto carico elevato.
Storia
Quando si utilizzava WaitGroup, si è dimenticato di chiamare Done() in diverse goroutine, il che ha portato a un'attesa eterna di Wait(), bloccando il thread principale. Di conseguenza, il servizio ha perso l'accessibilità fino a un riavvio manuale.