Ответ
Примитивы синхронизации (sync.Mutex
, sync.RWMutex
, sync.WaitGroup
, sync.Once
) всегда следует передавать по указателю.
Почему?
Примитивы синхронизации содержат внутреннее состояние, которое не должно копироваться. Например, sync.Mutex
имеет состояние, которое отслеживает, заблокирован ли он. Если вы передадите мьютекс по значению, функция получит его копию. Блокировка этой копии никак не повлияет на оригинальный мьютекс, что полностью нарушает логику синхронизации и может привести к состоянию гонки.
Статический анализатор go vet
по умолчанию отлавливает такие ошибки (правило copylocks
).
Правильный пример (по указателю):
func worker(id int, wg *sync.WaitGroup, m *sync.Mutex) {
defer wg.Done()
m.Lock()
fmt.Printf("Worker %d is workingn", id)
m.Unlock()
}
func main() {
var wg sync.WaitGroup
var m sync.Mutex
for i := 0; i < 3; i++ {
wg.Add(1)
go worker(i, &wg, &m) // Передаем по указателю
}
wg.Wait()
}
Неправильный пример (по значению):
// ЭТОТ КОД НЕПРАВИЛЬНЫЙ И ПРИВЕДЕТ К ОШИБКЕ ВЕТЕРА
func incorrectWorker(m sync.Mutex) {
m.Lock() // Блокируется копия, а не оригинал
defer m.Unlock()
}