Ответ
sync.Once
— это тип в стандартной библиотеке Go, который предоставляет механизм для выполнения определенного действия ровно один раз.
Основная задача sync.Once
— потокобезопасная ленивая инициализация. Он гарантирует, что даже при одновременном вызове из множества горутин, функция, переданная в метод Do
, будет выполнена только единожды.
Классические примеры использования:
- Инициализация синглтона (Singleton): Например, создание единственного экземпляра подключения к базе данных или загрузка конфигурации.
- Инициализация глобальных переменных: Когда требуется выполнить сложную или ресурсоемкую инициализацию при первом обращении.
Пример: Ленивая загрузка конфигурации
package main
import (
"fmt"
"sync"
)
var (
once sync.Once
config map[string]string
)
func loadConfig() {
fmt.Println("Загрузка конфигурации...")
// Эмуляция долгой операции
// time.Sleep(1 * time.Second)
config = map[string]string{
"api_key": "super-secret-key",
}
}
// GetConfig возвращает конфигурацию, инициализируя её при первом вызове.
func GetConfig() map[string]string {
once.Do(loadConfig) // `loadConfig` будет вызвана только один раз.
return config
}
func main() {
// Множество горутин могут одновременно вызывать GetConfig
// но `loadConfig` выполнится только один раз.
for i := 0; i < 10; i++ {
go GetConfig()
}
fmt.Scanln()
fmt.Println("Конфигурация получена:", GetConfig()["api_key"])
}
Ключевые особенности:
- Потокобезопасность: Внутренняя реализация использует мьютекс и атомарные операции для гарантии однократного выполнения.
- Надежность: Если функция, переданная в
Do
, паникует,sync.Once
считает, что вызов состоялся. Последующие вызовыDo
не будут выполнять эту функцию снова. - Идиоматичность: Это предпочтительный способ для однократной инициализации в Go, так как он проще и надежнее ручной реализации с помощью мьютекса и флага.