Ответ
Go предоставляет богатый набор примитивов синхронизации в пакетах sync и sync/atomic. Они используются для безопасной работы с общими данными в конкурентной среде.
Основные примитивы из пакета sync:
-
sync.Mutex(Мьютекс)- Назначение: Для обеспечения эксклюзивного доступа к критической секции кода. Только одна горутина может владеть блокировкой в один момент времени.
- Пример:
var mu sync.Mutex mu.Lock() // Блокируем доступ // ... критическая секция ... mu.Unlock() // Освобождаем
-
sync.RWMutex(Мьютекс чтения-записи)- Назначение: Оптимизация для сценариев, где чтений значительно больше, чем записей. Позволяет неограниченному числу горутин одновременно читать данные, но запись требует эксклюзивной блокировки.
- Пример:
var rwMu sync.RWMutex rwMu.RLock() // Блокировка для чтения rwMu.RUnlock() // Снятие блокировки чтения rwMu.Lock() // Блокировка для записи rwMu.Unlock() // Снятие блокировки записи
-
sync.WaitGroup- Назначение: Для ожидания завершения работы группы горутин. Основная горутина блокируется до тех пор, пока все дочерние горутины не сообщат о своем завершении.
- Пример:
var wg sync.WaitGroup wg.Add(1) // Увеличиваем счетчик go func() { defer wg.Done() // Уменьшаем счетчик при выходе // ... работа ... }() wg.Wait() // Ждем, пока счетчик не станет 0
-
sync.Once- Назначение: Гарантирует, что определенный участок кода будет выполнен только один раз за все время работы программы. Идеально для ленивой инициализации синглтонов.
- Пример:
var once sync.Once once.Do(func() { /* код инициализации */ })
-
sync.Pool- Назначение: Для переиспользования объектов, чтобы снизить нагрузку на сборщик мусора (GC). Полезно для временных объектов, которые часто создаются и уничтожаются.
-
sync.Cond(Условная переменная)- Назначение: Более сложный примитив, который позволяет горутинам ждать наступления определенного события или условия, широковещательно оповещая ожидающих.
Пакет sync/atomic
- Назначение: Предоставляет низкоуровневые атомарные операции (например,
Add,Load,Store,CompareAndSwap) для числовых типов. Эти операции выполняются без блокировок и являются самым быстрым способом для простых модификаций (например, счетчиков).
Каналы (chan)
Хотя каналы — это в первую очередь средство коммуникации, они также являются мощным примитивом синхронизации. Философия Go гласит: "Не общайтесь, разделяя память; разделяйте память, общаясь". Часто использование каналов является более идиоматичным и безопасным способом организации конкурентного кода, чем использование мьютексов.
Ответ 18+ 🔞
Слушай, а вот эти твои горутины, они ж как тараканы — бегают по всей программе, сука, одновременно. И если им не объяснить правила, они там в общей памяти такой пиздец устроят, что мама не горюй. Go, конечно, не оставил нас с этим один на один, подкинул целый арсенал в пакетах sync и sync/atomic. Разберём, что к чему, без соплей.
Основные кирпичи из sync:
-
sync.Mutex(Просто Мьютекс)- Зачем: Чтобы один уёбок мог влезть в критическую секцию, а все остальные ждали своей очереди, как в хорошей бане. Один владеет — остальные стоят.
- Как выглядит:
var mu sync.Mutex mu.Lock() // Всё, я вошёл, дверь закрыл. Кто следующий? Ждите. // ... делаю тут свои тёмные делишки с общими данными ... mu.Unlock() // Всё, выхожу. Следующий, заходи, ебашь.
-
sync.RWMutex(Мьютекс "Читай-Пиши")- Зачем: Для умных ситуаций, когда читателей — овердохуища, а писатель — один, да и тот редко. Позволяет всем читателям зайти одновременно и смотреть, но как только писатель пришёл — все нахуй выходят, и он один там всё переписывает.
- Пример:
var rwMu sync.RWMutex rwMu.RLock() // Я читатель, я тихо зашёл, других читателей не трогаю. rwMu.RUnlock() // Прочитал, вышел. rwMu.Lock() // Я писатель! ВСЕ НАХУЙ ВОН ИЗ КОМНАТЫ! (ждёт, пока читатели выйдут) rwMu.Unlock() // Написал, всех пускаю обратно.
-
sync.WaitGroup- Зачем: Чтобы главная горутина (мама-квочка) могла дождаться, пока все её ебучие дети-горутины закончат свои дела и доложат об этом. Иначе она завершится, а они останутся сиротами.
- Как это:
var wg sync.WaitGroup wg.Add(1) // Мама говорит: "Так, я жду одного ребёнка". go func() { defer wg.Done() // Ребёнок, выходя из функции, кричит: "Ма, я всё!" // ... ребёнок тут городит свою хуйню ... }() wg.Wait() // Мама сидит и ждёт на кухне, пока не услышит "Done" от всех.
-
sync.Once- Зачем: Чтобы какая-то хуйня выполнилась ровно ОДИН раз, даже если на неё будут тыкать из ста горутин. Классика — инициализация какого-нибудь глобального конфига.
- Пример:
var once sync.Once once.Do(func() { /* этот код выстрелит лишь раз, как пистолет с одним патроном */ })
-
sync.Pool- Зачем: Чтобы не сорить в памяти и не нагружать сборщик мусора. Создал объект, использовал — не выкидывай, положи в Pool. Кому-нибудь ещё пригодится. Экономия, блядь.
-
sync.Cond(Условная переменная)- Зачем: Для сложных танцев с бубном, когда горутины должны ждать не просто освобождения мьютекса, а наступления какого-то события. "Будите меня, когда придёт посылка" — вот это про
Cond.
- Зачем: Для сложных танцев с бубном, когда горутины должны ждать не просто освобождения мьютекса, а наступления какого-то события. "Будите меня, когда придёт посылка" — вот это про
Пакет sync/atomic — для низкоуровневых отморозков
- Суть: Тут тебе не мьютексы с блокировками, тут чистая атомарность на уровне процессора. Хочешь увеличить счётчик на 1 так, чтобы никакие другие ядра процессора даже пикнуть не успели?
atomic.AddInt64(&counter, 1). Быстро, жёстко, без лишних слов.
А теперь главная философия, ёпта!
Каналы (chan) — это не просто трубы для данных. Это ещё и крутейший инструмент синхронизации. Умные дядьки из Go тебе наказывают: "Хуй там общайтесь через общую память! Общайтесь, передавая сообщения!" Часто оказывается, что красивый канал — это куда идиоматичнее и надёжнее, чем вот эти все мьютексы с их вечными deadlock'ами, где все ждут друг друга и никто не работает. Подумай об этом, прежде чем везде пихать Lock().