ПрограммированиеBackend разработчик

Какие существуют разновидности синхронизации в Go? Как использовать sync.Mutex, sync.RWMutex и sync.WaitGroup, и какие тонкости есть в каждом случае?

Проходите собеседования с ИИ помощником Hintsage

Ответ

В Go для синхронизации конкурентных горутин используются структуры из пакета sync, наиболее частые — sync.Mutex, sync.RWMutex и sync.WaitGroup.

sync.Mutex предоставляет механизмы взаимного исключения при доступе к разделяемым данным. Его методы — Lock() (блокирует) и Unlock() (разблокирует).

sync.RWMutex расширяет возможности обычного мьютекса: допускает параллельное чтение, но эксклюзивное изменение.

sync.WaitGroup предназначен для ожидания завершения группы горутин. С помощью Add(int), Done() и Wait() вы управляете жизненным циклом параллельной работы.

Например:

var mu sync.RWMutex data := 0 // Чтение mu.RLock() fmt.Println(data) mu.RUnlock() // Запись mu.Lock() data = 42 mu.Unlock()

Тонкости:

  • Всегда используйте Unlock в блоке defer, чтобы не забыть разблокировать мьютекс даже при панике.
  • Убедитесь, что количество вызовов WaitGroup.Done() соответствует Add().
  • Не копируйте WaitGroup, Mutex и RWMutex!

Вопрос с подвохом

Можно ли захватить один и тот же sync.Mutex (или RWMutex) дважды подряд в одной и той же горутине? Что произойдёт?

Ответ: Нет, если вы вызовете Lock() на одном и том же Mutex дважды подряд без промежуточного Unlock(), горутина заблокируется навсегда (deadlock). В Go мьютексы нерекурсивные.

Пример:

var mu sync.Mutex mu.Lock() // ... mu.Lock() // DEADLOCK: заблокируемся навсегда, т.к. тот же поток уже держит замок

Примеры реальных ошибок


История

В проекте для high-load REST API разработчик обернул всю обработку запроса одним мьютексом. Это вызвало резкое падение производительности — только один запрос мог обрабатываться одновременно, хотя планировалось обслуживание тысяч клиентов. Причина — незнание разницы между Mutex и RWMutex и игнорирование параллельных чтений.


История

При копировании структуры с Mutex внутри одним из участников команды, случайно передали копию в другую функцию. Это привело к паническом сообщению "sync: copy of sync.Mutex" и крэшам в проде под высокой нагрузкой.


История

При использовании WaitGroup забыли вызвать Done() в нескольких горутинах, что привело к вечному ожиданию Wait(), блокирующему основной поток. В результате сервис терял доступность до ручной перезагрузки.