once
sync.Once es una herramienta de sincronización en la biblioteca estándar de Go, utilizada para asegurar que una función se ejecute solo una vez en entornos concurrentes. Se usa comúnmente en escenarios como inicialización perezosa, inicialización de recursos globales, etc., asegurando que una operación específica se ejecute solo una vez, incluso cuando múltiples goroutines se ejecutan concurrentemente.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var once sync.Once
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(func() {
fmt.Println(i)
})
}()
}
wg.Wait()
}En el código anterior, hay un total de 10 goroutines, pero sin importar qué, solo una goroutine ejecutará fmt.Println(i) dentro de once.Do(). Qué goroutine imprime depende de cuál goroutine llegue primero a once.Do().
Estructura
type Once struct {
done atomic.Uint32
m Mutex
}Su estructura interna es muy simple:
done, un valor atómico usado para indicar si ha sido ejecutadom, mutex, usado para bloquear otras goroutines que quieren ejecutar
Su principio es adquirir el lock antes de ejecutar, luego actualizar done, y liberar el lock después de completar la ejecución.
Do
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}El código es muy simple en general, el flujo es el siguiente:
- Carga directamente el valor atómico, si ya fue ejecutado, retorna directamente
- Si no ha sido ejecutado, intenta adquirir el lock. Aquí puede haber múltiples goroutines compitiendo por el mutex
- Cuando el ganador adquiere exitosamente el lock, aún necesita verificar nuevamente el valor de
done, porque mientras mantenías el lock, otro pudo haber completado el ciclo completo de adquirir lock-ejecutar-liberar lock. En este momento, acabas de ser despertado. - Ejecuta la función objetivo
- Actualiza el valor de
done - Libera el lock
Resumen
sync.Once es una herramienta de sincronización muy concisa y eficiente que asegura que una operación se ejecute solo una vez en entornos concurrentes, evitando así trabajo repetido o desperdicio de recursos.
