Код-ревью Senior sync и атомики обновлено
Код-ревью Go: ленивый синглтон и горячая перезагрузка конфига
Вопрос
Глобальный конфиг загружается лениво и перезагружается по сигналу. Найдите проблемы.
var (
instance *Config
mu sync.Mutex
)
func GetConfig() *Config {
if instance == nil {
mu.Lock()
if instance == nil {
instance = loadConfig()
}
mu.Unlock()
}
return instance
}
func (c *Config) Reload() {
newCfg := loadConfig()
*c = *newCfg
}
Коротко
Ленивая инициализация и горячая замена без гонок: Конфиг после публикации не меняется, поэтому читать его поля можно без блокировок.
Что должен найти ревьюер
- Ошибкастрока 7Double-checked locking без атомиков: чтение
instanceвне мьютекса это гонка данных. По модели памяти Go другая горутина может увидеть ненулевой указатель на ещё не полностью записанную структуру. Используйте sync.Once или sync.OnceValue. - Ошибкастрока 19Перезапись структуры на месте, пока другие горутины её читают: гонка, читатель может увидеть половину старых и половину новых полей. Нужна атомарная замена указателя через atomic.Pointer[Config].
- Стильстрока 10Ошибка загрузки конфига никак не обрабатывается: loadConfig должна возвращать error, а старт приложения падать с понятным сообщением.
Разбор
Ленивая инициализация и горячая замена без гонок:
var current atomic.Pointer[Config]
var initOnce = sync.OnceValue(func() error {
cfg, err := loadConfig()
if err != nil {
return err
}
current.Store(cfg)
return nil
})
func GetConfig() (*Config, error) {
if err := initOnce(); err != nil {
return nil, err
}
return current.Load(), nil
}
func Reload() error {
cfg, err := loadConfig()
if err != nil {
return err
}
current.Store(cfg) // читатели видят либо старый, либо новый конфиг целиком
return nil
}
Конфиг после публикации не меняется, поэтому читать его поля можно без блокировок.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Ещё по теме «sync и атомики»
- Глобальный максимум через CompareAndSwap
- Конкурентные переводы между счетами без дедлока
- Шардированная мапа с мьютексом на шард
- Что выведет Go: atomic.Int64 из 100 горутин
- Что выведет Go: sync.Once и OnceValue
- Атомики или мьютекс
- CompareAndSwap
- Флаг без синхронизации