Почему в канале Go без мьютекса не возникает проблем с конкурентным доступом?
Что проверяют
Знаете ли вы, что канал это не магия, а структура с внутренней блокировкой плюс гарантии модели памяти Go, и понимаете ли, где эта гарантия заканчивается. Заодно слушают, не считаете ли вы канал универсальной заменой мьютексу.
Как отвечать
Причин две, и назвать стоит обе.
Первая, техническая: у канала внутри есть свой мьютекс. Рантайм берёт его на каждой отправке и получении, поэтому буфер, очереди ожидающих горутин и счётчики не портятся. Гонки нет потому, что синхронизация уже сделана за вас, а не потому, что она не нужна.
Вторая, более важная: модель памяти Go задаёт отношение happens-before. Отправка в канал происходит раньше, чем завершится соответствующее получение. Значит всё, что горутина записала в память до отправки, видно получателю после получения. Это и есть передача владения: отправитель после отправки к значению больше не обращается.
go func() {
data := make([]int, 3)
data[0] = 42
ch <- data // дальше data трогать нельзя
}()
fmt.Println(<-ch)Дальше показать границу гарантии: если после отправки продолжить писать в тот же слайс или в объект по указателю, гонка есть, и канал от неё не спасает. Ловится она флагом -race у go test и go run.
Полезно добавить, что каналы не бесплатны: один счётчик дешевле защитить через sync.Mutex или пакет sync/atomic, а канал добавляет парковку горутины и лишний способ получить дедлок.
Где спотыкаются
Говорят, что канал делает данные потокобезопасными. Безопасен сам канал, а не то, что через него передали: указатель на общую карту, пролетев через канал, остаётся указателем на общую карту.
Утверждают, что отправка всегда блокирует до получения. Это верно для небуферизованного канала, а буферизованный принимает значения, пока есть место, поэтому из факта отправки нельзя заключить, что получатель уже прочитал.
Считают каналы всегда лучше мьютексов, ссылаясь на слоган про общение вместо разделяемой памяти. Слоган про архитектуру, а не про каждую переменную: на защите одного поля канал проигрывает и по скорости, и по числу способов повесить программу.