Гарантирует ли `sync.Mutex` справедливый (FIFO) порядок захвата блокировки?

Ответ

Нет, sync.Mutex в Go не гарантирует справедливый порядок захвата. Это означает, что горутина, которая дольше всех ждет освобождения мьютекса, не обязательно получит его первой. Такой подход называется "нечестным" (unfair) и выбран в пользу производительности.

Режимы работы sync.Mutex

Начиная с Go 1.9, мьютекс имеет два режима работы для предотвращения полного "голодания" (starvation) горутин:


  1. Нормальный режим (Normal mode): Это режим по умолчанию. Когда мьютекс освобождается, любая горутина — как уже ожидающая, так и только что подошедшая — может попытаться его захватить. Новые горутины имеют преимущество, так как они уже выполняются на процессоре, в то время как ожидающие нужно сначала "разбудить". Это повышает пропускную способность.



  2. Режим голодания (Starvation mode): Если горутина ожидает захвата мьютекса дольше 1 миллисекунды, мьютекс переходит в этот режим. В режиме голодания мьютекс передается строго в порядке очереди — той горутине, которая ждет дольше всех (FIFO). Это снижает производительность, но гарантирует, что ни одна горутина не будет ждать вечно.


Пример

var mu sync.Mutex

func worker(id int) {
    mu.Lock()
    defer mu.Unlock()
    fmt.Printf("Worker %d получил доступn", id)
    time.Sleep(10 * time.Millisecond)
}

func main() {
    for i := 1; i <= 5; i++ {
        go worker(i)
    }
    time.Sleep(time.Second)
}

Вывод этого кода будет непредсказуемым. Например, Worker 3 может получить доступ раньше Worker 1, даже если он был запущен позже.

Вывод: Для большинства случаев sync.Mutex работает эффективно, но если требуется строгий порядок выполнения, лучше использовать каналы.