Implementar el patrón Singleton en Go es una técnica útil cuando necesitas garantizar que solo exista una única instancia de un objeto en toda tu aplicación. El lenguaje Go ha sido diseñado con una fuerte inclinación hacia la simplicidad y la concurrencia segura. Una de sus primitivas menos conocidas, pero sumamente útiles, es sync.Once.
¿Qué es sync.Once?
sync.Once es una estructura del paquete sync de la biblioteca estándar de Go. Expone un único método:
func (o *Once) Do(f func())
Este método ejecuta la función f solo una vez, sin importar cuántas veces se llame desde múltiples goroutines. Todas las llamadas posteriores a Do se bloquean hasta que la función se haya completado, pero no volverán a ejecutarla.
Patrón Singleton con sync.Once
El patrón Singleton garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. En Go, con sync.Once, esta implementación es a prueba de race conditions y sin necesidad de bloqueos explícitos con sync.Mutex.
Ejemplo práctico:
package main
import (
"fmt"
"sync"
)
type Singleton struct {
data string
}
var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
fmt.Println("Creating Singleton instance")
instance = &Singleton{data: "I'm the only one!"}
})
return instance
}
func main() {
for i := 0; i < 5; i++ {
go func() {
fmt.Printf("%p\n", GetInstance())
}()
}
fmt.Scanln()
}
Salida esperada:
Creating Singleton instance
0xc000096010
0xc000096010
0xc000096010
0xc000096010
0xc000096010
Como se observa, sin importar cuántas veces se invoque GetInstance, se crea una sola instancia y todas las goroutines acceden a ella.
Casos de uso prácticos
- Carga de configuración global: Ideal para archivos .env, variables de entorno o YAML que deben cargarse una sola vez.
- Conexiones compartidas: Evita múltiples conexiones a recursos costosos como bases de datos o servicios externos.
- Inicialización de estructuras complejas: Como caches en memoria (ej.
sync.Mapo estructuras personalizadas). - Plugins o módulos registrados una sola vez: En sistemas modulares, evita registros repetidos de componentes.
Consideraciones importantes
- La función pasada a
Do()debe ser idempotente o segura ante fallos. Si entra en pánico o falla a mitad de ejecución, Do no volverá a intentar ejecutarla. - Si necesitas reintentos o reinicialización,
sync.Onceno es la herramienta adecuada. Considera usar un patrón consync.Mutexo control manual.
Conclusión
Aunque sync.Once puede parecer trivial, su utilidad en sistemas concurrentes es invaluable. Su rol en patrones como Singleton demuestra cómo Go, sin depender de herencia o clases, puede ofrecer soluciones limpias y eficientes a problemas clásicos de diseño.
En el mundo real, usar sync.Once correctamente puede ayudarte a evitar errores difíciles de depurar, como inicializaciones múltiples o conflictos de acceso a recursos compartidos. Si aún no lo has incorporado en tus proyectos, este puede ser el momento perfecto para comenzar.