Нужно ли использовать мьютексы для синхронизации при работе с каналами и горутинами в Go
Что проверяют
Понимаете ли вы, что канал сам по себе средство синхронизации, а мьютекс защищает общее изменяемое состояние. Вопрос сформулирован так, чтобы увидеть, не станете ли вы обкладывать канал мьютексом на всякий случай.
Как отвечать
Канал уже даёт гарантию порядка: отправка значения происходит раньше, чем завершается соответствующий приём, а закрытие раньше, чем приём нулевого значения из закрытого канала. Операции с каналом безопасны из нескольких горутин, дополнительный мьютекс вокруг них не нужен и только создаёт риск дедлока.
Мьютекс нужен там, где состояние общее и остаётся на месте: счётчик, кэш, карта, структура с несколькими полями, которые надо менять согласованно. Встроенная карта не потокобезопасна, конкурентная запись роняет весь процесс, и это не паника, которую можно перехватить.
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
cache[key] = valueРазумный выбор такой: канал, когда данные передаются и владение переходит вместе со значением; sync.Mutex или sync.RWMutex, когда данные лежат на месте и к ним ходят многие; sync/atomic для счётчиков и флагов. Канал ради одного числа это лишняя горутина и лишняя сложность. Проверяется всё это детектором гонок, он встроен в тулчейн и включается флагом сборки.
Где спотыкаются
Говорят, что мьютексы приводят к состоянию гонки. Гонку создаёт незащищённый доступ, а неверное применение мьютекса даёт другое: дедлок, залипание на блокировке, потерю параллелизма.
Отправляют в канал указатель и продолжают писать в объект после отправки. Канал синхронизировал передачу ссылки, а не владение данными, и получатель читает структуру, которую в этот момент меняет отправитель.
Копируют структуру, внутри которой лежит sync.Mutex. У копии своя блокировка, защита разъезжается, и код молча работает без синхронизации. На передачу таких структур по значению ругается go vet.