once
sync.Once — это инструмент синхронизации в стандартной библиотеке Go, используемый для гарантии, что функция выполняется только один раз в конкурентной среде. Он обычно используется в таких сценариях, как ленивая инициализация, инициализация глобальных ресурсов и т.д., гарантируя, что определённая операция выполняется только один раз, даже когда несколько goroutine выполняются одновременно.
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()
}В приведённом коде всего 10 goroutine, но независимо только одна goroutine выполнит fmt.Println(i) в once.Do(). Какая goroutine напечатает, зависит от того, какая goroutine первая достигнет once.Do().
Структура
type Once struct {
done atomic.Uint32
m Mutex
}Его внутренняя структура очень проста:
done, атомарное значение, используется для указания, было ли выполненоm, блокировка мьютекса, используется для блокировки других goroutine, которые хотят выполнить
Его принцип заключается в блокировке перед выполнением, затем обновлении done и разблокировке после завершения выполнения.
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()
}
}Код в целом очень прост, со следующим потоком:
- Напрямую загружает атомарное значение, если уже выполнено, немедленно возвращается
- Если не выполнено, пытается приобрести блокировку, здесь несколько goroutine могут конкурировать за мьютекс
- После того как победитель успешно приобрёл блокировку, он всё ещё должен проверить значение
doneснова, потому что после приобретения блокировки кто-то другой мог уже завершить последовательность приобретение-блокировки-выполнение-разблокировка, и вы только что были пробуждены. - Выполняет целевую функцию
- Обновляет значение
done - Освобождает блокировку
Итоги
sync.Once — это очень лаконичный и эффективный инструмент синхронизации. Он гарантирует, что определённая операция выполняется только один раз в конкурентной среде, тем самым избегая дублирования работы или потери ресурсов.
