Lecciones del módulo (4/5)
sync.Mutex y sync.WaitGroup
Los canales son el idioma principal de Go, pero a veces son más tradicionales.
Se necesitan primitivos. El paquete sync proporciona mutex, espere
grupos y otras utilidades para coordinar el estado compartido.
sync.Mutex: bloqueo exclusivo
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
}Solo una gorutina a la vez ingresa entre Lock() y Unlock().
Patrón obligatorio: defer mu.Unlock() justo después de Lock().
sync.RWMutex: muchos lectores, un escritor
Para estado de lectura intensa:
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()Útil sólo si las lecturas realmente dominan sobre las escrituras; de lo contrario un regular
sync.Mutex es más simple y a menudo más rápido gracias a una menor interna
contención.
sync.WaitGroup: espera N gorutinas
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 arrivatiFlujo de trabajo:
wg.Add(N)incrementa el contador en N (antes de lanzar las gorutinas).- Cada gorutina llama a
wg.Done()cuando finaliza (idealmente a través dedefer). wg.Wait()se bloquea hasta que el contador vuelve a 0.
sync.Once: inicialización diferida segura para subprocesos
var (
once sync.Once
config *Config
)
func GetConfig() *Config {
once.Do(func() {
config = loadConfig()
})
return config
}La devolución de llamada en once.Do se ejecuta exactamente una vez, incluso en condiciones concurrentes.
llamadas. Patrón de inicialización singleton/perezoso.
Mutex vs canal: cuándo usar qué
Directrices (también de las preguntas frecuentes oficiales de Go):
| Caso | Herramienta preferida |
|---|---|
| Transferencia de propiedad de los datos | canal |
| Distribución de trabajo (cola de trabajo) | canal |
| Coordinación de gorutinas independientes | canal |
| Protegiendo un campo de estructura | Mutex |
| Caché/contador | Mutex o atómico |
| Referencia única | CÓDIGOPH0 |
"Canales para orquestar, exclusiones mutuas para datos compartidos" es una buena regla general.
Detector de carrera
Ejecute pruebas con -race para detectar carreras de datos:
go test -race ./...
go run -race main.goEl detector de carreras instrumenta el binario y registra todos los no sincronizados. acceso a la memoria compartida. Es la herramienta número 1 para validar código concurrente. antes de la producción.
Pruébalo
Proteja el incremento del recuento con mu.Lock() + diferir mu.Unlock().
Mostrar pista
diferir mu.Unlock() justo después de Lock() garantiza la liberación incluso en caso de pánico.
Solución disponible después de 3 intentos
Inicie 3 goroutines y espere todas ellas usando sync.WaitGroup (Add(3), Done() en cada goroutine, Wait()).
Mostrar pista
Agregue ANTES de iniciar la rutina; Hecho DENTRO de la rutina con aplazamiento.
Solución disponible después de 3 intentos
¿Cuál es el patrón recomendado para garantizar que se libere el mutex?
mu.Lock()
// ?Resumen
sync.Mutexprotege el estado compartido; siempre CÓDIGOPH1.- Utilice un receptor de puntero para evitar copiar el mutex.
sync.RWMutexpara patrones de muchos lectores/un escritor (solo si ayuda).sync.WaitGroup:Addantes dego,Donecondefer,Waita la espera.sync.Once: inicialización diferida segura para subprocesos.- "Canales para orquestar, mutexes para datos compartidos".
defer mu.Unlock()0: herramienta n.° 1 para detectar carreras de datos.