Какие основные примитивы синхронизации предоставляет Go и для каких задач они применяются?

Ответ

Go предоставляет богатый набор примитивов синхронизации в пакетах sync и sync/atomic. Они используются для безопасной работы с общими данными в конкурентной среде.

Основные примитивы из пакета sync:

  1. sync.Mutex (Мьютекс)

    • Назначение: Для обеспечения эксклюзивного доступа к критической секции кода. Только одна горутина может владеть блокировкой в один момент времени.
    • Пример:
      var mu sync.Mutex
      mu.Lock() // Блокируем доступ
      // ... критическая секция ...
      mu.Unlock() // Освобождаем
  2. sync.RWMutex (Мьютекс чтения-записи)

    • Назначение: Оптимизация для сценариев, где чтений значительно больше, чем записей. Позволяет неограниченному числу горутин одновременно читать данные, но запись требует эксклюзивной блокировки.
    • Пример:
      var rwMu sync.RWMutex
      rwMu.RLock()   // Блокировка для чтения
      rwMu.RUnlock() // Снятие блокировки чтения
      rwMu.Lock()    // Блокировка для записи
      rwMu.Unlock()  // Снятие блокировки записи
  3. sync.WaitGroup

    • Назначение: Для ожидания завершения работы группы горутин. Основная горутина блокируется до тех пор, пока все дочерние горутины не сообщат о своем завершении.
    • Пример:
      var wg sync.WaitGroup
      wg.Add(1) // Увеличиваем счетчик
      go func() {
          defer wg.Done() // Уменьшаем счетчик при выходе
          // ... работа ...
      }()
      wg.Wait() // Ждем, пока счетчик не станет 0
  4. sync.Once

    • Назначение: Гарантирует, что определенный участок кода будет выполнен только один раз за все время работы программы. Идеально для ленивой инициализации синглтонов.
    • Пример:
      var once sync.Once
      once.Do(func() { /* код инициализации */ })
  5. sync.Pool

    • Назначение: Для переиспользования объектов, чтобы снизить нагрузку на сборщик мусора (GC). Полезно для временных объектов, которые часто создаются и уничтожаются.
  6. sync.Cond (Условная переменная)

    • Назначение: Более сложный примитив, который позволяет горутинам ждать наступления определенного события или условия, широковещательно оповещая ожидающих.

Пакет sync/atomic

  • Назначение: Предоставляет низкоуровневые атомарные операции (например, Add, Load, Store, CompareAndSwap) для числовых типов. Эти операции выполняются без блокировок и являются самым быстрым способом для простых модификаций (например, счетчиков).

Каналы (chan)

Хотя каналы — это в первую очередь средство коммуникации, они также являются мощным примитивом синхронизации. Философия Go гласит: "Не общайтесь, разделяя память; разделяйте память, общаясь". Часто использование каналов является более идиоматичным и безопасным способом организации конкурентного кода, чем использование мьютексов.