sync.Once y Patrones Singleton
sync.Once ejecuta una función exactamente una vez, incluso cuando muchas goroutines la llaman concurrentemente.
Busca en todas las páginas de la documentación
sync.Once ejecuta una función exactamente una vez, incluso cuando muchas goroutines la llaman concurrentemente.
Los equipos lo usan para la inicialización perezosa de recursos costosos, evitando al mismo tiempo los peligros de orden de init().
Un singleton expone una instancia compartida para todo el programa.
Go puede implementar eso con variables a nivel de paquete, pero el código de producción idiomático prefiere la inyección explícita de dependencias y reserva sync.Once para casos como el registro de codecs o la carga de raíces TLS donde una configuración perezosa única es inevitable.
La biblioteca estándar usa sync.Once internamente para la inicialización única; tu código de aplicación debería usarlo con moderación y documentar por qué se justifican los globales.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
var (
parser *regexp.Regexp
once sync.Once
)
func emailPattern() *regexp.Regexp {
once.Do(func() {
parser = regexp.MustCompile(`^[^@]+@[^@]+\.[^@]+$`)
})
return parser
}Cuándo usar esto:
main no puede pasar fácilmente dependencias a pilas de llamadas profundaspackage metrics
import (
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type Registry struct {
requests *prometheus.CounterVec
}
var (
defaultReg *Registry
once sync.Once
)
func Default() *Registry {
once.Do(func() {
defaultReg = &Registry{
requests: promauto.NewCounterVec(
prometheus.CounterOpts{Name: "http_requests_total", Help: "HTTP requests"},
[]string{"route", "code"},
),
}
})
return defaultReg
}
func (r *Registry) Inc(route, code string) {
r.requests.WithLabelValues(route, code).Inc()
}
func ExampleUsage() {
Default().Inc("/health", "200")
}Lo que esto demuestra:
once.Do inicializa defaultReg en la primera llamada a Default()Default() exportado documenta el punto de acceso globalsync.Once almacena una bandera de finalización y un mutexDo ejecuta f; los concurrentes en espera se bloquean hasta que se completeDo se convierte en una carga atómica rápidaf entra en pánico, Once se queda atascado; no entres en pánico dentro de Do| Mecanismo | Se ejecuta cuándo | Riesgo |
|---|---|---|
init() | Carga del paquete, orden fijo | Efectos secundarios de importación, fallos no probables |
sync.Once | Primera llamada explícita | Global oculto si se usa en exceso |
Constructor en main | Inicio, explícito | Mejor capacidad de prueba |
| Enfoque | Capacidad de prueba | Simplicidad |
|---|---|---|
Parámetros desde main | Alta | Requiere cableado |
sync.Once global de paquete | Baja | Conveniente |
init() global | La más baja | Más fácil de escribir |
// Prefiere esto en el código de aplicación:
func NewAPI(reg *metrics.Registry) *API { return &API{reg: reg} }
// En main:
reg := metrics.NewRegistry()
api := NewAPI(reg)once.Do con funciones diferentes; solo se ejecuta la primerahttp.DefaultClient en todo el proceso, prefiere un cliente explícito con tiempo de esperaOnce nunca reintenta; el paquete permanece roto. Solución: valida antes de Do o entra en pánico solo ante errores de programador que aceptas como fatales.*sql.DB desde main o un ayudante de prueba.sync.Once perezoso o sin red de inicialización.Default() solo para demostraciones.sync.Once en lugar de atómicos personalizados.Once debe vivir en la estructura si cada instancia se inicializa por separado. Solución: Once a nivel de paquete solo para verdaderos singletons.| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| Inyección de dependencias | Servicios de aplicación, APIs | Caché de regex interna |
init() para constantes puras | Registro de controladores, tablas matemáticas | Configuración de red o de entorno |
sync.Once inicialización perezosa | Configuración costosa única | Cada campo de estructura |
Fábrica en main | Múltiples entornos (prod/stage) | Biblioteca que oculta recursos |
Los singletons mutables a nivel de paquete se desaconsejan en el código de aplicación porque perjudican las pruebas y ocultan dependencias.
sync.Once para configuración perezosa inmutable es aceptable en bibliotecas.
No; crea un nuevo valor Once en una nueva estructura si necesitas una configuración única por instancia.
Después de la primera llamada, Do es una carga atómica única, insignificante en rutas activas.
golang.org/x/sync/singleflight desduplica el trabajo concurrente por clave; relacionado pero no lo mismo que las instancias singleton.
Solo cuando el ecosistema lo espera (patrón http.DefaultClient).
Prefiere New que devuelve instancias configuradas.
Refactoriza para aceptar interfaces, o prueba a través de la API pública aceptando estado global compartido como pruebas de integración.
El orden de inicialización sigue el grafo de importación; las importaciones cíclicas fallan.
Mantén init() libre de E/S.
La inicialización anticipada en main falla rápidamente al inicio; la inicialización perezosa pospone el costo hasta el primer uso y puede ocultar una mala configuración.
Sí; sync.Once sigue siendo el estándar para la configuración única independientemente de los genéricos.
Los operadores construyen un gestor en main y pasan clientes explícitamente, lo que se prefiere a los clientes singleton.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado de GC Green Tea, go fix modernizers - verifica el parche en la compilación), chi (última versión - verifica en la compilación), gin (última versión - verifica en la compilación), echo (última versión - verifica en la compilación), google.golang.org/grpc (última versión - verifica en la compilación), sigs.k8s.io/controller-runtime (última versión - verifica en la compilación), kubebuilder (última versión - verifica en la compilación), tinygo (última versión - verifica los objetivos de placa en la compilación), wazero (última versión - verifica en la compilación) y golangci-lint (última versión - verifica en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026