Достаточно ли одного флага для реализации корректного мьютекса (Mutex)?

Ответ

Нет, одного атомарного флага (например, 0 - свободен, 1 - занят) абсолютно недостаточно для реализации корректного и честного мьютекса в конкурентной среде.

Проблема с одним флагом: Race Condition

Если использовать только флаг, возникнет классическое состояние гонки "проверил-установил" (check-then-set):

  1. Горутина А проверяет флаг. Он равен 0 (свободен).
  2. Планировщик Go приостанавливает Горутину А.
  3. Горутина Б проверяет флаг. Он все еще 0.
  4. Горутина Б устанавливает флаг в 1 и входит в критическую секцию.
  5. Планировщик возобновляет Горутину А. Она "помнит", что флаг был свободен, и тоже устанавливает его в 1 и входит в критическую секцию.

В итоге две горутины одновременно оказались в критической секции, что полностью нарушает смысл мьютекса.

Как на самом деле устроен sync.Mutex в Go

Реализация sync.Mutex значительно сложнее и опирается на два ключевых механизма:


  1. Атомарные операции: Для изменения состояния мьютекса используются низкоуровневые атомарные инструкции (например, CompareAndSwap), которые выполняются как единая, неделимая операция, что решает проблему "проверил-установил".



  2. Очередь ожидания (паркование горутин): Если мьютекс занят, горутина не крутится в цикле, проверяя флаг (это называется spinlock и неэффективно тратит CPU). Вместо этого она "паркуется" с помощью внутреннего механизма планировщика Go (через семафор). Она переходит в состояние ожидания и не потребляет ресурсы CPU. Когда мьютекс освобождается, планировщик "будит" одну из ожидающих горутин.


Кроме того, sync.Mutex в Go имеет два режима работы для обеспечения "честности" и производительности:

  • Normal Mode: Режим по умолчанию. Разблокированный мьютекс может быть захвачен как новой горутиной, так и той, что уже давно стоит в очереди. Это эффективно, но может привести к тому, что ожидающие горутины будут "голодать".
  • Starvation Mode: Если горутина ждет дольше 1 мс, мьютекс переходит в этот режим. В нем право на захват передается строго первой горутине в очереди ожидания. Это предотвращает голодание, но ценой небольшого снижения пропускной способности.

Таким образом, sync.Mutex — это сложная структура, обеспечивающая не только взаимное исключение, но и эффективное управление ожидающими горутинами.