Что такое `sync.Once` в Go? Опишите его назначение, приведите пример использования и расскажите об особенностях.

Ответ

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Все горутины завершили работу. Инициализация была вызвана только один раз.")
}

Ключевые особенности


  1. Потокобезопасность: sync.Once гарантирует, что даже при одновременном вызове Do() из множества горутин, переданная функция будет выполнена только одной из них. Остальные горутины будут заблокированы до тех пор, пока первая не завершит выполнение.



  2. Отсутствие сброса: В sync.Once нет метода для сброса состояния в «невыполненное». Однажды выполненное действие нельзя «откатить» и запустить заново с помощью того же экземпляра Once.



  3. Поведение при панике: Если функция, переданная в Do(), вызывает панику, sync.Once считает, что вызов состоялся. Паника будет передана горутине, которая вызвала Do(). Все последующие вызовы Do() с этим же экземпляром once не будут выполнять функцию и не будут паниковать, они просто немедленно вернут управление.