once
sync.Once é uma ferramenta de sincronização da biblioteca padrão do Go, usada para garantir que uma função seja executada apenas uma vez em ambiente concorrente. É comumente usado em cenários como inicialização tardia, inicialização de recursos globais e outros, garantindo que uma operação específica seja executada apenas uma vez, mesmo que múltiplas goroutines executem concorrentemente.
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()
}No código acima, há um total de 10 goroutines, mas independentemente disso, apenas uma goroutine executará o fmt.Println(i) dentro de once.Do(). Qual goroutine imprimirá depende de qual goroutine chegar primeiro ao once.Do().
Estrutura
type Once struct {
done atomic.Uint32
m Mutex
}Sua estrutura interna é muito simples:
done, um valor atômico usado para indicar se já foi executadom, mutex de exclusão mútua, usado para bloquear outras goroutines que desejam executar
Seu princípio é travar antes de executar, depois atualizar done, e destravar após a execução.
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()
}
}O código é extremamente simples no geral, com o seguinte fluxo:
- Carrega diretamente o valor atômico. Se já foi executado, retorna diretamente
- Se não foi executado, tenta obter o lock. Aqui pode haver múltiplas goroutines competindo pelo mutex
- Quando o vencedor obtém o lock com sucesso, ainda precisa verificar novamente o valor de
done, pois enquanto você mantém o lock, outra goroutine pode já ter completado o ciclo de travar-executar-destravar. Neste momento você acabou de ser despertado - Executa a função alvo
- Atualiza o valor de
done - Libera o lock
Resumo
sync.Once é uma ferramenta de sincronização muito concisa e eficiente, garantindo que uma operação específica seja executada apenas uma vez em ambiente concorrente, evitando assim trabalho repetido ou desperdício de recursos.
