> Что будет если мьютекс передается по значению в Go? (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Ютека

Стек: Go

> Пример ответа

Короткий ответ

Передача мьютекса по значению в Go приводит к копированию структуры sync.Mutex, что ломает его гарантии взаимного исключения. После копирования состояние мьютекса (включая флаг блокировки и очередь ожидающих) не синхронизируется между копиями, поэтому разные горутины будут работать с разными мьютексами. Это классическая ошибка, которую детектирует go vet с сообщением copies lock value. В итоге защищаемые данные становятся доступны конкурентно без блокировок, что приводит к data race.

Подробное объяснение

sync.Mutex - это структура, которая содержит внутреннее состояние: флаг блокировки, счётчик ожидающих горутин и указатель на очередь. Когда вы передаёте мьютекс по значению (например, в функцию или метод), Go создаёт полную копию этой структуры. Копия не разделяет состояние с оригиналом - это два независимых объекта.

Ключевой момент: мьютекс предназначен для использования через указатель (*sync.Mutex), потому что его состояние должно быть общим для всех горутин, которые соревнуются за доступ к критической секции. При копировании:

  • Оригинальный мьютекс остаётся заблокированным, но копия - разблокированной.
  • Горутина, получившая копию, может успешно вызвать Lock(), даже если оригинал уже заблокирован.
  • Две горутины, работающие с разными копиями, не будут блокировать друг друга.

Это нарушает инвариант мьютекса: в любой момент времени только одна горутина может владеть блокировкой. Вместо этого получается несколько независимых "мьютексов", каждый из которых защищает свою копию данных - но данные-то общие.

Дополнительная проблема: после копирования заблокированного мьютекса его состояние не определено. В документации Go прямо сказано: "A Mutex must not be copied after first use". Это означает, что даже копирование до первого использования (когда мьютекс ещё не блокировался) технически допустимо, но крайне опасно, потому что любая будущая блокировка на одной копии не будет видна другой.

На практике

В реальном коде эта ошибка чаще всего встречается в трёх сценариях:

  1. Передача мьютекса в функцию - например, func process(m sync.Mutex) вместо func process(m *sync.Mutex).
  2. Хранение мьютекса в структуре, которая копируется - если структура с мьютексом передаётся по значению или возвращается из функции, мьютекс копируется вместе с ней.
  3. Использование мьютекса как поля структуры, которая передаётся по значению - например, type SafeCounter struct { mu sync.Mutex; value int }, а затем func (s SafeCounter) Inc() - здесь s - копия, и мьютекс копируется.

На практике это приводит к трудноуловимым гонкам данных, которые не всегда воспроизводятся стабильно. Особенно опасно, когда структура с мьютексом передаётся в несколько горутин через канал или как аргумент go func(obj MyStruct).

Правильный подход: всегда использовать указатель на мьютекс (*sync.Mutex) или хранить мьютекс в структуре и передавать указатель на структуру. Также стоит помнить, что go vet автоматически находит такие ошибки, поэтому запуск статического анализа перед коммитом - обязательная практика.

Пример кода

GO
package main
import (
"fmt"
"sync"
)
// Неправильно: мьютекс передаётся по значению
func badIncrement(mu sync.Mutex, counter *int) {
mu.Lock()
*counter++
mu.Unlock()
}
// Правильно: мьютекс передаётся по указателю
func goodIncrement(mu *sync.Mutex, counter *int) {
mu.Lock()
*counter++
mu.Unlock()
}
func main() {
var mu sync.Mutex
counter := 0
// Плохой пример: каждая горутина получит свою копию мьютекса
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
badIncrement(mu, &counter) // копия мьютекса!
}()
}
wg.Wait()
fmt.Println("bad counter:", counter) // скорее всего не 100
// Хороший пример: все горутины используют один и тот же мьютекс
counter = 0
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
goodIncrement(&mu, &counter) // указатель на мьютекс
}()
}
wg.Wait()
fmt.Println("good counter:", counter) // всегда 100
}

Как отвечать на собеседовании

Начните с прямого ответа: копирование мьютекса ломает синхронизацию. Затем объясните механизм - структура sync.Mutex содержит внутреннее состояние, которое не разделяется между копиями. Приведите пример сценария, где это происходит: передача по значению, копирование структуры с мьютексом, возврат структуры из функции. Упомяните, что go vet детектирует эту проблему. Обязательно скажите, что правильное решение - использовать указатель. Если спросят про sync.RWMutex - ответ аналогичный, он тоже не должен копироваться. Хорошо добавить, что в Go есть специальное поле noCopy в структуре мьютекса, которое помогает статическим анализаторам находить такие ошибки.

Что проверяет интервьюер

Интервьюер проверяет:

  • Понимание того, что мьютекс - это не просто флаг, а структура с состоянием.
  • Знание семантики передачи по значению в Go (копирование структур).
  • Понимание модели конкурентности: мьютекс должен быть общим для всех горутин.
  • Осведомлённость об инструментах статического анализа (go vet).
  • Умение предложить правильное решение (указатель) и объяснить, почему оно работает.
  • Глубину: знает ли кандидат про noCopy поле и про то, что копирование после первого использования - неопределённое поведение.

Типичные ошибки

  • Ответ "ничего страшного, просто скопируется" - неверно, потому что копия не синхронизирует доступ.
  • Предложение использовать sync.Mutex как значение в структуре без указателя - это тоже ошибка, если структура копируется.
  • Утверждение, что "Go запрещает копирование мьютекса" - на самом деле это не запрещено на уровне компилятора, только go vet предупреждает.
  • Игнорирование того, что копирование заблокированного мьютекса - неопределённое поведение, а не просто логическая ошибка.
  • Рекомендация использовать sync.RWMutex как "решение" - это не решает проблему копирования, RWMutex тоже нельзя копировать.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью