Тип sync.Once

Тип sync.Once из пакета sync предоставляет механизм, который гарантирует, что переданная функция будет выполнена ровно один раз, независимо от того, сколько горутин вызывает ее одновременно. Это особенно полезно для инициализации синглтонов, настройки глобальных ресурсов или выполнения однократной настройки приложения.

Синтаксис

govar once sync.Once
once.Do(func())

Метод Do принимает функцию без параметров в качестве аргумента и выполняет ее только в первый раз. Последующие вызовы Do не выполняют функцию и возвращают управление немедленно.

Пример

Давайте рассмотрим базовый пример использования sync.Once для однократного выполнения функции инициализации:

gopackage main

import (
  "fmt"
  "sync"
)

func main() {
  var once sync.Once

  once.Do(func() {
    fmt.Println("Initialization performed")
  })

  once.Do(func() {
    fmt.Println("This will not be printed")
  })
}

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

go"Initialization performed"

Пример

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

gopackage main

import (
  "fmt"
  "sync"
)

func main() {
  var once sync.Once
  var wg sync.WaitGroup

  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
      defer wg.Done()
      once.Do(func() {
        fmt.Printf("Worker %d: initialization done\n", id)
      })
    }(i)
  }

  wg.Wait()
}

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

go"Worker 3: initialization done"

Пример

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

gopackage main

import (
  "fmt"
  "sync"
)

type Config struct {
  Settings map[string]string
}

var (
  config *Config
  once   sync.Once
)

func GetConfig() *Config {
  once.Do(func() {
    fmt.Println("Initializing config")
    config = &Config{
      Settings: map[string]string{
        "key": "value",
      },
    }
  })
  return config
}

func main() {
  var wg sync.WaitGroup

  for i := 0; i < 3; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      cfg := GetConfig()
      fmt.Println(cfg.Settings["key"])
    }()
  }

  wg.Wait()
}

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

go"Initializing config"
"value"
"value"
"value"

Пример

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

gopackage main

import (
  "fmt"
  "sync"
)

func main() {
  var once sync.Once

  once.Do(func() {
    fmt.Println("First call")
    panic("something went wrong")
  })

  once.Do(func() {
    fmt.Println("This will not be printed")
  })

  fmt.Println("Program continues")
}

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

go"First call"
"panic: something went wrong"

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

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