Метод sync.Once.Do

Метод sync.Once.Do принимает в качестве аргумента функцию без параметров и возвращаемых значений. Он гарантирует, что эта функция будет вызвана только один раз, независимо от количества горутин, которые одновременно вызывают Do на одном и том же экземпляре sync.Once. Если функция вызывается повторно, Do ничего не делает и возвращает управление. Это идеальный механизм для ленивой инициализации объектов, загрузки конфигураций или создания синглтонов.

Синтаксис

govar once sync.Once
once.Do(func() {
  // код, который должен выполниться один раз
})

Базовый пример

В этом примере sync.Once.Do используется для инициализации глобальной переменной. Несмотря на то, что мы запускаем несколько горутин, инициализация произойдет только один раз.

gopackage main

import (
  "fmt"
  "sync"
)

var (
  config string
  once   sync.Once
)

func loadConfig() {
  config = "config loaded"
  fmt.Println("Config loaded once")
}

func main() {
  var wg sync.WaitGroup

  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      once.Do(loadConfig)
      fmt.Println("Config:", config)
    }()
  }

  wg.Wait()
}

Результат выполнения кода (порядок строк может отличаться, но сообщение о загрузке появится только один раз):

go"Config loaded once"
"Config: config loaded"
"Config: config loaded"
"Config: config loaded"
"Config: config loaded"
"Config: config loaded"

Пример с паникой

Важно: если функция, переданная в Do, вызывает панику, то Once считает, что вызов не был завершен успешно, и при следующем вызове Do выполнит функцию снова. Это поведение позволяет восстановиться после ошибок инициализации.

gopackage main

import (
  "fmt"
  "sync"
)

func main() {
  var once sync.Once
  counter := 0

  for i := 0; i < 3; i++ {
    func() {
      defer func() {
        if r := recover(); r != nil {
          fmt.Println("recovered from:", r)
        }
      }()

      once.Do(func() {
        counter++
        fmt.Println("attempt:", counter)
        if counter == 1 {
          panic("initialization failed")
        }
      })
    }()
  }

  fmt.Println("final counter:", counter)
}

Результат выполнения кода:

go"attempt: 1"
"recovered from: initialization failed"
"attempt: 2"
"final counter: 2"

Смотрите также

  • тип sync.Once,
    который предоставляет механизм однократного выполнения
  • тип sync.WaitGroup,
    который используется для ожидания завершения горутин
  • тип sync.Mutex,
    который обеспечивает взаимное исключение при доступе к данным
  • тип sync.RWMutex,
    который предоставляет блокировку с разделяемым доступом на чтение