В чем разница spinlock и mutex
Что проверяют
Понимаете ли вы, чем активное ожидание отличается от засыпания и когда каждое выгодно. В Go следом почти всегда спрашивают, как устроен sync.Mutex внутри и стоит ли писать свой spinlock на atomic.
Как отвечать
- spinlock: поток в цикле пытается захватить флаг атомарной операцией (CAS) и не отдаёт процессор, пока не получится. Переключения контекста нет, но ожидание сжигает CPU. Оправдан, когда блокировку держат считаные инструкции, а заснуть нельзя, как в обработчиках прерываний ядра;
- mutex: ожидающий засыпает и просыпается, когда владелец отпустит блокировку. CPU свободен, но засыпание и пробуждение дороже короткого ожидания.
Дальше покажите, что граница размыта. Незанятый мьютекс берётся одной атомарной операцией без системного вызова (в Linux на этом построен futex), а sync.Mutex гибридный: при конкуренции он сначала немного крутится, если ядер больше одного, и только потом паркует горутину через семафор рантайма. Паркуется горутина, а не поток ОС: планировщик сажает на поток другую горутину, и это дешевле переключения потоков ядром.
Ещё деталь: у sync.Mutex есть режим голодания. Если горутина ждёт дольше 1 мс, мьютекс переходит в режим FIFO и передаёт владение напрямую первому в очереди, чтобы новые горутины не перехватывали его бесконечно.
Итог для Go: в прикладном коде sync.Mutex или sync.RWMutex, для счётчиков и флагов sync/atomic, а свой spinlock только с бенчмарком на руках.
Где спотыкаются
Говорят, что мьютекс всегда означает системный вызов и переключение контекста. Без конкуренции это одна атомарная инструкция.
Предлагают самописный spinlock на atomic.CompareAndSwapInt32 «для скорости». Горутину, которая его держит, планировщик может вытеснить, и остальные будут крутиться впустую, занимая процессоры.
Путают уровни: утверждают, что Lock() в Go усыпляет поток ОС. Засыпает горутина, поток продолжает выполнять другие.