Объясните, как нулевое значение sync.Mutex влияет на его готовность к использованию в Go.
Нулевое значение sync.Mutex уже представляет разблокированный мьютекс, поэтому его можно использовать без конструктора или отдельной инициализации. Достаточно объявить поле или переменную типа sync.Mutex и вызывать Lock и Unlock.
В Go многие типы синхронизации спроектированы так, чтобы их нулевое значение было практически пригодно к работе. Это уменьшает количество обязательной инициализации, упрощает структуры данных и позволяет безопасно встраивать мьютекс непосредственно в пользовательский тип.
Для sync.Mutex такое поведение означает: после создания переменная находится в состоянии «разблокирован». Отдельная функция-конструктор для обычного мьютекса не требуется.
Если разработчик ошибочно считает, что мьютекс нужно сначала создать специальным вызовом, он усложняет код без необходимости. Более опасная ошибка — скопировать мьютекс после начала использования: копия может содержать внутреннее состояние, не согласованное с исходным мьютексом, что приводит к некорректной синхронизации.
Мьютекс защищает только тот общий ресурс, доступ к которому действительно окружён блокировкой. Само наличие поля sync.Mutex не делает остальные операции над данными автоматически безопасными.
Переменная sync.Mutex в нулевом состоянии разблокирована. Первый вызов Lock успешно захватывает её, последующие вызовы от других горутин блокируются до вызова Unlock владельцем критической секции.
Мьютекс обычно встраивают в структуру и используют через указатель на неё, чтобы не копировать структуру вместе с уже используемым мьютексом. Копирование допустимо только до начала использования и лишь при условии, что копии действительно должны стать независимыми объектами.
В примере Counter не имеет конструктора: поле mu сразу готово к использованию. Указатель на Counter важен не только для изменения n, но и для предотвращения случайного копирования мьютекса при вызове методов.
Критическую секцию следует делать минимальной. Нельзя удерживать мьютекс дольше необходимого, например во время медленного сетевого вызова, если защищаемое состояние уже прочитано.
В сервисе есть объект с кэшем и счётчиком обращений. Вариант с глобальным мьютексом прост, но блокирует доступ ко всем объектам сразу и может снизить параллелизм. Вариант с отдельным мьютексом для каждого объекта требует аккуратного управления временем жизни, но уменьшает конкуренцию.
Практичным решением становится мьютекс как поле самого объекта: его нулевое значение готово к работе, а блокировка защищает только состояние конкретного экземпляра. Это устраняет лишнюю инициализацию и позволяет нескольким независимым объектам обслуживаться параллельно.
Можно ли копировать структуру с нулевым, ещё не использованным мьютексом?
Технически копирование до первого использования не переносит активное состояние блокировки, поэтому копии могут стать независимыми мьютексами. Однако после начала использования копировать sync.Mutex нельзя: документация пакета sync прямо требует не копировать такой объект после первого использования.
Достаточно ли защищать запись мьютексом, если чтение выполняется без блокировки?
Нет. Параллельное чтение и запись одной переменной без согласованной синхронизации создают гонку данных. Все обращения к состоянию должны быть защищены одним и тем же мьютексом либо выполняться с использованием подходящего атомарного примитива.
Что произойдёт, если вызвать Unlock для уже разблокированного мьютекса?
Это ошибка использования примитива и приводит к панике. sync.Mutex не хранит информацию о логическом владельце, поэтому разблокировать его должен именно код, который корректно захватил мьютекс и завершает соответствующую критическую секцию. Часто defer mu.Unlock() сразу после успешного Lock помогает не забыть освобождение при раннем возврате.