sync.Once
Каждый запрос к S3 поднимал подключение и грузил креды заново. Миша перевёл инициализацию на один раз: первый запрос готовил клиента, остальные дожидались готового. Повторные запросы пошли заметно быстрее.
sync.Once гарантирует, что функция выполнится ровно один раз, сколько бы горутин ни вызвали Do — и все дождутся её завершения.
Ленивая инициализация
var (
once sync.Once
s3Client *S3Client
)
func GetS3Client() *S3Client {
once.Do(func() {
s3Client = newS3Client()
})
return s3Client
}
Do выполняет функцию один раз. Первый вызвавший запускает newS3Client, остальные, пришедшие во время выполнения, блокируются и после возврата Do видят готовый s3Client. Это каноническая ленивая инициализация — без проверок «а не инициализировали ли уже».
Паника «засчитывается»
once.Do(func() {
panic("инициализация упала")
})
once.Do(func() {
fmt.Println("не выполнится")
})
Если функция внутри Do паникует, Once считает вызов выполненным: повторный Do ничего не запустит. Для инициализации это значит: упала — навсегда. Если нужна возможность «попробовать ещё раз», Once не подходит.
Где Once уместен
Одноразовая инициализация: создание S3-клиента, пула соединений, регистрация обработчиков. Везде, где «сделать один раз» важнее, чем «сделать при старте». Отличие от init: Once — лениво, в момент первого обращения, а не при загрузке пакета.
Вы узнаете:
- чем
Onceотличается отinit()и проверкиif nilпод мьютексом - как happens-before работает через Once
Проверьте себя
once.Do запускает функцию с panic. Что при втором вызове Do?
Чем Once лучше ручной двойной проверки под мьютексом?
После завершения once.Do все читатели видят инициализированную структуру целиком?
правильных ответов
Once — «ровно один раз» с ожиданием остальных. Паника засчитывается. Это ленивая инициализация для вещей, которые создаются дорого.
10 000 ₽ · 60 минут · онлайн