sync.Once & Padrões Singleton
sync.Once executa uma função exatamente uma vez, mesmo quando muitas goroutines a chamam concorrentemente.
Busque em todas as páginas da documentação
sync.Once executa uma função exatamente uma vez, mesmo quando muitas goroutines a chamam concorrentemente.
Equipes o utilizam para inicialização preguiçosa de recursos caros, evitando problemas de ordem de init().
Um singleton expõe uma instância compartilhada para todo o programa.
Go pode implementar isso com variáveis em nível de pacote, mas o código de produção idiomático prefere injeção explícita de dependência e reserva sync.Once para casos como registro de codecs ou carregamento de raízes TLS, onde uma única configuração preguiçosa é inevitável.
A biblioteca padrão usa sync.Once internamente para inicialização única; seu código de aplicação deve usá-lo com moderação e documentar por que os globais são justificados.
Cartão de receita de referência rápida - pronto para copiar e colar.
var (
parser *regexp.Regexp
once sync.Once
)
func emailPattern() *regexp.Regexp {
once.Do(func() {
parser = regexp.MustCompile(`^[^@]+@[^@]+\.[^@]+$`)
})
return parser
}Quando usar isso:
main não pode facilmente passar dependências para pilhas de chamadas 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")
}O que isso demonstra:
once.Do inicializa defaultReg na primeira chamada de Default()Default() exportado documenta o ponto de acesso globalsync.Once armazena uma flag de conclusão e um mutexDo executa f; esperadores concorrentes bloqueiam até a conclusãoDo se torna uma carga atômica rápidaf entrar em pânico, Once fica travado - não entre em pânico dentro de Do| Mecanismo | Executa quando | Risco |
|---|---|---|
init() | Carregamento do pacote, ordem fixa | Efeitos colaterais de importação, falhas não testáveis |
sync.Once | Primeira chamada explícita | Global oculto se usado em excesso |
Construtor em main | Inicialização, explícito | Melhor testabilidade |
| Abordagem | Testabilidade | Simplicidade |
|---|---|---|
Parâmetros de main | Alta | Requer injeção |
sync.Once global de pacote | Baixa | Conveniente |
init() global | Mais Baixa | Mais fácil de escrever |
// Prefira isso em código de aplicação:
func NewAPI(reg *metrics.Registry) *API { return &API{reg: reg} }
// Em main:
reg := metrics.NewRegistry()
api := NewAPI(reg)once.Do com funções diferentes - apenas a primeira é executadahttp.DefaultClient em todo o processo, prefira um cliente explícito com timeoutOnce nunca tenta novamente; o pacote permanece quebrado. Correção: valide antes de Do ou entre em pânico apenas em erros de programador que você aceita como fatais.*sql.DB de main ou helper de teste.sync.Once preguiçoso ou sem rede em init.Default() oculto em todos os lugares - Assinaturas omitem dependências; fakes impossíveis. Correção: injete interface; mantenha Default() apenas para demonstrações.sync.Once em vez de atômicos personalizados.Once deve viver na struct se cada instância inicializa separadamente. Correção: Once em nível de pacote apenas para verdadeiros singletons.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Injeção de dependência | Serviços de aplicação, APIs | Cache de regex interno |
init() para constantes puras | Registro de drivers, tabelas matemáticas | Configuração de rede ou ambiente |
sync.Once inicialização preguiçosa | Configuração única e custosa | Cada campo de struct |
Fábrica em main | Múltiplos ambientes (prod/stage) | Biblioteca ocultando recursos |
Singletons mutáveis em nível de pacote são desencorajados em código de aplicação porque prejudicam os testes e ocultam dependências.
sync.Once para configuração preguiçosa imutável é aceitável em bibliotecas.
Não - crie um novo valor Once em uma nova struct se precisar de configuração única por instância.
Após a primeira chamada, Do é uma única carga atômica - negligenciável em caminhos críticos.
golang.org/x/sync/singleflight deduplica trabalho concorrente por chave - relacionado, mas não o mesmo que instâncias singleton.
Apenas quando o ecossistema espera isso (padrão http.DefaultClient).
Prefira New retornando instâncias configuradas.
Refatore para aceitar interfaces, ou teste através da API pública aceitando estado global compartilhado como testes de integração.
A ordem de init segue o grafo de importação; importações cíclicas falham.
Mantenha init() livre de I/O.
Inicialização antecipada em main falha rapidamente na inicialização; inicialização preguiçosa adia o custo até o primeiro uso e pode ocultar má configuração.
Sim - sync.Once permanece o padrão para configuração única, independentemente de genéricos.
Operadores constroem um manager em main e passam clientes explicitamente - preferível a clientes singleton.
Versões de Stack: Esta página foi escrita para Go 1.26.x (padrão GC Green Tea, go fix modernizers - verifique o patch na compilação), chi (última - verifique na compilação), gin (última - verifique na compilação), echo (última - verifique na compilação), google.golang.org/grpc (última - verifique na compilação), sigs.k8s.io/controller-runtime (última - verifique na compilação), kubebuilder (última - verifique na compilação), tinygo (última - verifique os alvos de placa na compilação), wazero (última - verifique na compilação) e golangci-lint (última - verifique na compilação).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026