Зачем нужен sync.Once

Ответ

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

Пример:

var (
    once sync.Once
    config map[string]string
)

func loadConfig() {
    once.Do(func() {
        // Этот код выполнится только один раз, независимо от количества вызовов loadConfig
        config = readConfigFromFile() 
    })
}

// Вспомогательная функция для примера
func readConfigFromFile() map[string]string {
    // Имитация чтения конфигурации
    return map[string]string{"key": "value"}
}

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

  • Потокобезопасность: Гарантирует корректное выполнение в конкурентной среде.
  • Однократное выполнение: Функция, переданная в Do, будет вызвана только один раз за всё время жизни программы.
  • Игнорирование последующих вызовов: После первого успешного выполнения Do, все последующие вызовы once.Do не будут выполнять переданную функцию, но будут ждать завершения первого выполнения, если оно еще не закончено.
  • Применение: Идеально подходит для инициализации синглтонов, загрузки конфигураций, установки соединений с базами данных или других операций, которые должны произойти ровно один раз.

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

Ответ 18+ 🔞

Да ты посмотри, какой хитрожопый примитив придумали! sync.Once, блядь. Сидит себе такой тихий, скромный, а делает вещь — просто пиздец какая важная.

Представь: у тебя есть какая-нибудь ресурсоёмкая хуйня, которую надо сделать один раз и всем потом пользоваться. Например, конфиг загрузить, к базе подключиться, или там глобальную переменную инициализировать. И делать это надо из кучи горутин одновременно, а они все как сумасшедшие ломятся, кричат «я первая!», «нет, я!».

Вот тут-то он и выходит на сцену, наш герой — sync.Once. Берёт он твою функцию, оборачивает её в свой Do, и говорит: «Не волнуйся, сука, я тут всё улажу». И улаживает, ёпта! Какой бы пиздец ни творился вокруг, сколько бы горутин ни орало «выполни меня!», он эту функцию запустит ровно один раз. Один, Карл! А все остальные вызовы просто послушно подождут, пока первый не закончится, и потом пойдут дальше, как ни в чём не бывало. Волшебство, блядь!

Вот смотри, как это выглядит в коде, тут всё просто, как три копейки:

var (
    once sync.Once // Вот он, наш стражник однократности
    config map[string]string
)

func loadConfig() {
    once.Do(func() { // Всё, что внутри этой анонимки, случится один-единственный раз в жизни программы
        // Допустим, тут чтение с диска, сетевой запрос — что угодно долгое и тяжёлое
        config = readConfigFromFile() 
    })
    // После этого можно смело использовать config, он гарантированно проинициализирован
}

Ключевые фишки, которые делают его охуенным:

  • Потокобезопасность на уровне ядра. Можно вызывать из двадцати тысяч горутин — он не обосрётся, не выполнит код дважды и не вызовет гонку данных. Просто красота.
  • Один раз и навсегда. Выполнил и забыл. Все последующие вызовы once.Do — это просто вежливые кивки в его сторону. Функция внутри не выполнится, но горутина подождёт, если первый вызов ещё не завершился. Никаких race condition, всё чётко.
  • Применение — да везде, где надо один раз! Синглтоны, ленивая инициализация, подключение к чему-либо, загрузка ресурсов. В общем, любая операция, от повторного выполнения которой у тебя волосы дыбом встанут.

Короче, sync.Once — это как надёжный замок на двери, которую надо открыть один раз. Сколько бы народу ни ломилось, откроет её только первый, а остальные спокойно пройдут внутрь, когда всё будет готово. Элементарно, Ватсон, но без этого в конкурентном мире — никуда, блядь.