Ce face sync.Mutex
Când mai multe goroutine modifică aceeași variabilă (un contor, un map, un slice), rezultatul poate ieși greșit sau programul se poate opri cu eroarea concurrent map writes. sync.Mutex rezolvă problema: mu.Lock() „încuie” lacătul, iar dacă alt goroutine îl ține deja, așteaptă până când acela apelează mu.Unlock(). Codul dintre cele două apeluri (secțiunea critică) rulează deci de un singur goroutine o dată.
Valoarea zero este un mutex descuiat, gata de folosit: nu trebuie inițializat. Modelul obișnuit este mu.Lock() urmat imediat de defer mu.Unlock(), ca lacătul să fie eliberat și dacă funcția iese mai devreme. De regulă mutexul stă în aceeași structură cu datele pe care le protejează.
Un mutex nu se copiază după prima folosire. Structurile care conțin un mutex se transmit prin pointer, iar metodele lor au receptor pointer (func (c *Cont) ...). go vet semnalează copierile greșite.
Ce returnează
Lock și Unlock nu returnează nimic. Unlock pe un mutex descuiat oprește programul cu eroare fatală.
Exemple
Exemplul 1: Contor sigur pentru 100 de goroutine
Fiecare goroutine adaugă 1000 la contor. Cu mutex, rezultatul este mereu exact 100000.
package main
import (
"fmt"
"sync"
)
func main() {
var (
mu sync.Mutex
contor int
wg sync.WaitGroup
)
for range 100 {
wg.Add(1)
go func() {
defer wg.Done()
for range 1000 {
mu.Lock()
contor++
mu.Unlock()
}
}()
}
wg.Wait()
fmt.Println("Contor:", contor)
}
Rezultat
Contor: 100000
Exemplul 2: Cont bancar protejat de mutex
Mutexul stă în structură, metodele au receptor pointer și folosesc defer pentru Unlock. Retragerile care ar duce soldul pe minus sunt refuzate corect chiar dacă vin în paralel.
package main
import (
"fmt"
"sync"
)
type Cont struct {
mu sync.Mutex
sold int
}
func (c *Cont) Retrage(suma int) bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.sold < suma {
return false
}
c.sold -= suma
return true
}
func (c *Cont) Sold() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.sold
}
func main() {
cont := &Cont{sold: 1000}
var wg sync.WaitGroup
var mu sync.Mutex
reusite := 0
for range 15 {
wg.Add(1)
go func() {
defer wg.Done()
if cont.Retrage(100) {
mu.Lock()
reusite++
mu.Unlock()
}
}()
}
wg.Wait()
fmt.Println("Retrageri reușite:", reusite)
fmt.Println("Sold final:", cont.Sold(), "lei")
}
Rezultat
Retrageri reușite: 10
Sold final: 0 lei
Exemplul 3: Fără mutex: rezultat greșit
Același contor fără lacăt. contor++ înseamnă citește, adună, scrie; două goroutine pot citi aceeași valoare și o adunare se pierde. Rezultatul diferă la fiecare rulare, iar go run -race raportează problema.
package main
import (
"fmt"
"sync"
)
func main() {
contor := 0
var wg sync.WaitGroup
for range 100 {
wg.Add(1)
go func() {
defer wg.Done()
for range 1000 {
contor++ // cursă de date!
}
}()
}
wg.Wait()
fmt.Println("Contor:", contor)
}
Rezultat posibil (diferă la fiecare rulare)
Contor: 38517
Greșeli frecvente
- Uiți
Unlockpe un drum de ieșire (unreturnîntr-unif). Toate celelalte goroutine rămân blocate; foloseștedefer mu.Unlock(). - Apelezi
Lockde două ori din același goroutine (de exemplu o metodă blocată apelează altă metodă care blochează același mutex). Mutexul din Go nu este reentrant: programul se blochează. - Copiezi o structură cu mutex (receptor valoare
func (c Cont)sau transmitere prin valoare). Fiecare copie are lacătul ei și protecția dispare.
Comenzi înrudite
sync.RWMutex: Lacăt pentru citire și scriere: mai mulți cititori pot lucra deodată (RLock), dar un scriitor lucrează singur (Lock).sync.Mutex.TryLock: Încearcă să încuie mutexul fără să aștepte: returnează true dacă a reușit, false dacă lacătul este ocupat.sync.WaitGroup: Așteaptă ca un grup de goroutine să termine: Add mărește contorul, Done îl scade, Wait blochează până ajunge la zero.sync/atomic.Int64: Un int64 cu operații atomice ca metode (Add, Load, Store, Swap); varianta recomandată pentru contoare între goroutine.