Для чего предназначен пакет `sync` в Go и какие основные примитивы он предоставляет?

Ответ

Пакет sync предоставляет примитивы синхронизации, которые используются для управления конкурентным доступом к общим ресурсам из нескольких горутин и для координации их работы. Его главная цель — предотвращение состояния гонки (data race).

Основные примитивы:

  • sync.Mutex (Mutual Exclusion) — обеспечивает эксклюзивный доступ к критической секции кода. Только одна горутина может владеть мьютексом в любой момент времени.

    var mu sync.Mutex
    mu.Lock() // Блокирует доступ для других горутин
    // ... критическая секция ...
    mu.Unlock() // Освобождает блокировку
  • sync.RWMutex (Read/Write Mutex) — более гранулярная блокировка. Позволяет любому количеству горутин одновременно читать данные, но запись возможна только при эксклюзивной блокировке. Идеален для данных, которые читаются гораздо чаще, чем изменяются.

  • sync.WaitGroup — позволяет дождаться завершения работы группы горутин. Основные методы: Add() для увеличения счетчика горутин, Done() для его уменьшения (вызывается в конце работы горутины) и Wait() для блокировки до тех пор, пока счетчик не станет равен нулю.

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1) // Увеличиваем счетчик
        go func() {
            defer wg.Done() // Уменьшаем счетчик при выходе из горутины
            // ... полезная работа ...
        }()
    }
    wg.Wait() // Ожидаем, пока все горутины завершатся
  • sync.Once — гарантирует, что определенный участок кода будет выполнен только один раз за все время работы программы, независимо от количества вызовов. Часто используется для ленивой инициализации (например, синглтонов).

  • sync.Pool — предоставляет кеш временных объектов для повторного использования, что помогает снизить нагрузку на сборщик мусора (GC) в высокопроизводительных приложениях.

  • sync.Map — потокобезопасный аналог map, оптимизированный для сценариев, где ключи в основном читаются или обновляются, но редко добавляются новые.