Что будет если мьютекс передается по значению в Go?
Что проверяют
Понимаете ли вы, что sync.Mutex это обычная структура с внутренним состоянием, и что копирование структуры копирует состояние блокировки. За вопросом стоит более общий: держите ли вы в голове разницу между значением и ссылкой в Go.
Как отвечать
sync.Mutex не хранит указателя на общий объект, всё его состояние лежит в полях самой структуры. Копия становится независимым мьютексом, и взаимное исключение перестаёт работать: одна горутина держит оригинал, вторая спокойно заходит в критическую секцию через копию.
Хуже, когда копируют уже заблокированный мьютекс: копия рождается заблокированной, и первый же Lock на ней виснет навсегда. А Unlock копии, снятой до блокировки, роняет программу с сообщением про разблокировку незаблокированного мьютекса.
Практически это всплывает не в явной передаче мьютекса, а через структуру, которая его содержит:
type Cache struct {
mu sync.Mutex
m map[string]int
}
func (c Cache) Get(k string) int { // получатель по значению копирует мьютекс
c.mu.Lock()
defer c.mu.Unlock()
return c.m[k]
}Правило простое: у типа с мьютексом все методы берут получателя по указателю, а сам тип передаётся и хранится только по указателю. Отдельно скажите, что такую ошибку ловит go vet проверкой copylocks, и что она запускается вместе с go test.
Где спотыкаются
Отвечают «будет паника». Паники не будет, и в этом опасность: программа продолжит работать, гонка проявится под нагрузкой, а go test -race поймает её, только если тест действительно устраивает параллельный доступ.
Второе: помнят про мьютекс и забывают про остальные типы из sync. WaitGroup, Cond, Once, RWMutex копировать нельзя ровно так же, а WaitGroup, переданный в горутину по значению, даёт вечное ожидание.
Третье: считают, что достаточно один раз написать получателя по указателю. Копия появляется и при присваивании структуры в переменную, при отправке в канал, при добавлении в срез и при возврате из функции по значению.