Ce face sync.RWMutex
sync.RWMutex are două feluri de blocare. RLock/RUnlock sunt pentru citire: oricâte goroutine pot citi în același timp. Lock/Unlock sunt pentru scriere: scriitorul așteaptă să termine toți cititorii și nimeni altcineva nu intră cât timp scrie.
Merită folosit când datele sunt citite des și modificate rar: un cache, o configurare, o listă de prețuri. Dacă scrierile sunt dese, un sync.Mutex simplu este la fel de bun și mai ușor de înțeles.
rw.RLocker() returnează un sync.Locker ale cărui Lock/Unlock apelează de fapt RLock/RUnlock. Este util când o funcție primește un sync.Locker oarecare și vrei să-i dai partea de citire.
Ce returnează
Metodele de blocare nu returnează nimic. RLocker returnează un sync.Locker.
Exemple
Exemplul 1: Cache de prețuri: mulți cititori, un scriitor
package main
import (
"fmt"
"sync"
)
type Preturi struct {
mu sync.RWMutex
date map[string]float64
}
func (p *Preturi) Pret(produs string) (float64, bool) {
p.mu.RLock()
defer p.mu.RUnlock()
v, ok := p.date[produs]
return v, ok
}
func (p *Preturi) Seteaza(produs string, pret float64) {
p.mu.Lock()
defer p.mu.Unlock()
p.date[produs] = pret
}
func main() {
p := &Preturi{date: map[string]float64{"pâine": 4.5, "lapte": 7.2}}
var wg sync.WaitGroup
gasite := make([]float64, 10)
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
gasite[i], _ = p.Pret("lapte")
}()
}
wg.Wait()
fmt.Println("10 citiri în paralel:", gasite)
p.Seteaza("lapte", 7.9)
v, _ := p.Pret("lapte")
fmt.Println("După scumpire:", v)
_, ok := p.Pret("cafea")
fmt.Println("Cafea în listă:", ok)
}
Rezultat
10 citiri în paralel: [7.2 7.2 7.2 7.2 7.2 7.2 7.2 7.2 7.2 7.2]
După scumpire: 7.9
Cafea în listă: false
Exemplul 2: RLocker: partea de citire ca sync.Locker
Funcția cuLacat primește orice sync.Locker. Cu RLocker(), două apeluri pot ține lacătul de citire în același timp.
package main
import (
"fmt"
"sync"
)
func cuLacat(l sync.Locker, f func()) {
l.Lock()
defer l.Unlock()
f()
}
func main() {
var rw sync.RWMutex
cititor := rw.RLocker()
cuLacat(cititor, func() {
// suntem în RLock: un alt cititor poate intra, un scriitor nu
fmt.Println("Al doilea cititor poate intra:", rw.TryRLock())
rw.RUnlock()
fmt.Println("Un scriitor poate intra: ", rw.TryLock())
})
cuLacat(&rw, func() {
fmt.Println("Acum scriem, cititorii așteaptă:", !rw.TryRLock())
})
}
Rezultat
Al doilea cititor poate intra: true
Un scriitor poate intra: false
Acum scriem, cititorii așteaptă: true
Greșeli frecvente
- Modifici datele sub
RLock. Cititorii lucrează în paralel, deci scrierea subRLockeste tot o cursă de date; pentru modificări foloseșteLock. - Încerci să „promovezi” un
RLockînLockfără să eliberezi întâi citirea:Lockașteaptă să plece toți cititorii, inclusiv tu, și programul se blochează. - Apelezi
RLockrecursiv (o funcție cuRLockapelează alta care face totRLock). Dacă între timp așteaptă un scriitor, al doileaRLockse blochează.
Comenzi înrudite
sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().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.Map: Un map sigur pentru goroutine fără mutex extern: Store, Load, Delete, Range și Clear pot fi apelate concurent.sync/atomic.Pointer: Un pointer *T citit și înlocuit atomic, cu tip verificat la compilare; varianta modernă a lui atomic.Value.