sync.Mutex между переменнымиsync.Mutex не просто структура с двумя полями. Это объект с состоянием, и если вы присвоите один мьютекс другому во время работы горутин, получите либо панику, либо гонку данных.Что происходит при присвоении
Посмотрите на этот код:
var mu1 sync.Mutex
var mu2 sync.Mutex
// Горутина 1: захватывает mu1
go func() {
mu1.Lock()
defer mu1.Unlock()
// работа...
}()
// Горутина 2: заменяет mu1 на mu2
go func() {
mu1 = mu2 // замена мьютекса прямо во время использования
}()
Здесь два опасных сценария.
Сценарий 1: паника
Горутина 1 захватила
mu1. Горутина 2 присвоила mu1 = mu2. Теперь mu1 — это чистый, незахваченный мьютекс. Когда горутина 1 дойдёт до Unlock(), она попытается разблокировать мьютекс, который никто не блокировал, и получит панику:unlock of unlocked mutex
Присвоение переменных не создаёт отношения happens-before между горутинами, в отличие от запуска и завершения горутины. Поэтому порядок, в котором горутина 1 увидит изменение
mu1, непредсказуем.Сценарий 2: гонка данных
Если горутина 1 разблокирует
mu1 в тот же момент, когда горутина 2 делает присвоение, обе одновременно записывают в поля state и sema внутри структуры sync.Mutex. Это классическая гонка данных. Итог — повреждение состояния одного или обоих мьютексов.Как делать правильно
Не присваивайте мьютексы напрямую. Если нужно передать мьютекс в другое место, используйте указатель:
var mu sync.Mutex
// Передаём указатель, а не копию
go func(m *sync.Mutex) {
m.Lock()
defer m.Unlock()
// работа...
}(&mu)
Это же правило распространяется на структуры, которые содержат
sync.Mutex. Копировать их тоже нельзя. Только передавать по указателю:type SafeCounter struct {
mu sync.Mutex
count int
}
// Правильно
func increment(c *SafeCounter) {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
// Неправильно: мьютекс скопируется вместе со структурой
func incrementWrong(c SafeCounter) {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}Инструмент
go vet умеет находить такие копирования и выдаёт предупреждение copylocks.sync.Mutex нельзя копировать или присваивать после первого использования. Передавайте мьютексы и структуры с ними только по указателю. 📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoDeep