Какие основные примитивы синхронизации предоставляет 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 гласит: "Не общайтесь, разделяя память; разделяйте память, общаясь". Часто использование каналов является более идиоматичным и безопасным способом организации конкурентного кода, чем использование мьютексов.

Ответ 18+ 🔞

Слушай, а вот эти твои горутины, они ж как тараканы — бегают по всей программе, сука, одновременно. И если им не объяснить правила, они там в общей памяти такой пиздец устроят, что мама не горюй. 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() // Мама сидит и ждёт на кухне, пока не услышит "Done" от всех.
  4. sync.Once

    • Зачем: Чтобы какая-то хуйня выполнилась ровно ОДИН раз, даже если на неё будут тыкать из ста горутин. Классика — инициализация какого-нибудь глобального конфига.
    • Пример:
      var once sync.Once
      once.Do(func() { /* этот код выстрелит лишь раз, как пистолет с одним патроном */ })
  5. sync.Pool

    • Зачем: Чтобы не сорить в памяти и не нагружать сборщик мусора. Создал объект, использовал — не выкидывай, положи в Pool. Кому-нибудь ещё пригодится. Экономия, блядь.
  6. sync.Cond (Условная переменная)

    • Зачем: Для сложных танцев с бубном, когда горутины должны ждать не просто освобождения мьютекса, а наступления какого-то события. "Будите меня, когда придёт посылка" — вот это про Cond.

Пакет sync/atomic — для низкоуровневых отморозков

  • Суть: Тут тебе не мьютексы с блокировками, тут чистая атомарность на уровне процессора. Хочешь увеличить счётчик на 1 так, чтобы никакие другие ядра процессора даже пикнуть не успели? atomic.AddInt64(&counter, 1). Быстро, жёстко, без лишних слов.

А теперь главная философия, ёпта!

Каналы (chan) — это не просто трубы для данных. Это ещё и крутейший инструмент синхронизации. Умные дядьки из Go тебе наказывают: "Хуй там общайтесь через общую память! Общайтесь, передавая сообщения!" Часто оказывается, что красивый канал — это куда идиоматичнее и надёжнее, чем вот эти все мьютексы с их вечными deadlock'ами, где все ждут друг друга и никто не работает. Подумай об этом, прежде чем везде пихать Lock().