Ответ
sync.Once
— это структура из стандартного пакета sync
, которая предоставляет механизм для выполнения определенного действия (функции) ровно один раз, вне зависимости от того, сколько горутин и сколько раз попытаются это сделать.
Основное назначение
Основной паттерн использования sync.Once
— ленивая потокобезопасная инициализация. Это идеально подходит для задач, где дорогостоящие или критичные ресурсы должны быть созданы только один раз при первом к ним обращении.
Типичные примеры:
- Инициализация синглтона (Singleton).
- Парсинг конфигурационного файла.
- Создание пула соединений с базой данных.
Пример использования
В этом примере несколько горутин одновременно пытаются выполнить инициализацию. Благодаря sync.Once
функция setup()
будет вызвана только один раз.
package main
import (
"fmt"
"sync"
"time"
)
var once sync.Once
// setup - функция, которую мы хотим выполнить только один раз.
func setup() {
fmt.Println("Инициализация ресурсов...")
// Здесь может быть логика подключения к БД, чтение конфига и т.д.
time.Sleep(100 * time.Millisecond) // Имитация долгой операции
fmt.Println("Инициализация завершена.")
}
func worker(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Горутина пытается выполнить инициализацию.")
// Каждая горутина пытается выполнить setup()
once.Do(setup)
}
func main() {
var wg sync.WaitGroup
// Запускаем 10 горутин, которые одновременно попытаются выполнить инициализацию
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
fmt.Println("nВсе горутины завершили работу. Инициализация была вызвана только один раз.")
}
Ключевые особенности
Потокобезопасность:
sync.Once
гарантирует, что даже при одновременном вызовеDo()
из множества горутин, переданная функция будет выполнена только одной из них. Остальные горутины будут заблокированы до тех пор, пока первая не завершит выполнение.Отсутствие сброса: В
sync.Once
нет метода для сброса состояния в «невыполненное». Однажды выполненное действие нельзя «откатить» и запустить заново с помощью того же экземпляраOnce
.Поведение при панике: Если функция, переданная в
Do()
, вызывает панику,sync.Once
считает, что вызов состоялся. Паника будет передана горутине, которая вызвалаDo()
. Все последующие вызовыDo()
с этим же экземпляромonce
не будут выполнять функцию и не будут паниковать, они просто немедленно вернут управление.