Как правильно передавать примитивы синхронизации (например, `sync.Mutex`) в функции?

Ответ

Примитивы синхронизации (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()
}