Один раз в Go
Иногда тяжелую настройку или запись в общую переменную нужно сделать единожды, хотя к ней обращаются из многих горутин. Повторный запуск даст лишнюю работу или испортит состояние, если код не рассчитан на второй заход.
Тип sync.Once хранит флаг завершения.
Метод Do принимает функцию без
аргументов: первый вызов выполняет ее
тело, все последующие из любых веток
сразу выходят, не заходя внутрь.
Три горутины зовут одну инициализацию:
package main
import (
"fmt"
"sync"
)
var setup sync.Once
var ready bool
func initOnce() {
setup.Do(func() {
ready = true
fmt.Println("init")
})
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
defer wg.Done()
initOnce()
}()
}
wg.Wait()
fmt.Println("ready", ready)
}
Строка "init" печатается один раз,
хотя initOnce вызвали трижды.
ready остается истиной после
единственного прохода внутри Do.
Тот же прием для счетчика вызовов:
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
var hits int
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
defer wg.Done()
once.Do(func() {
hits++
fmt.Println("once")
})
}()
}
wg.Wait()
fmt.Println("hits", hits)
}
Дан следующий код:
var flag sync.Once
var msg string
func fill() {
flag.Do(func() {
msg = "ok"
})
}
Запустите fill из двух веток
с ожиданием группы и выведите msg.
Дан следующий код:
var o sync.Once
func run() {
o.Do(func() {
fmt.Println("A")
})
o.Do(func() {
fmt.Println("B")
})
}
Скажите, сколько букв напечатает один
вызов run.
Скажите, зачем передавать в Do
функцию, а не вызывать обычный код
рядом с проверкой флага вручную.