В чем разница между Mutex и Spinlock в C++?

«В чем разница между Mutex и Spinlock в C++?» — вопрос из категории Многопоточность, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Mutex — блокирующий примитив синхронизации. Если поток пытается захватить уже занятый мьютекс, он переводится в состояние ожидания (sleep), освобождая процессорное время. Это эффективно для критических секций, выполнение которых занимает относительно долгое время, так как избегает бесполезного потребления CPU.

Spinlock — неблокирующий примитив, реализующий активное ожидание (busy-waiting). Поток в цикле проверяет, освободилась ли блокировка, не отдавая процессор. Это оправдано для очень коротких критических секций (например, несколько инструкций), где накладные расходы на переключение контекста и усыпление потока могут превысить время самого ожидания.

Ключевые отличия:

  • Поведение при занятости: Mutex — sleep, Spinlock — busy-wait.
  • Накладные расходы: Mutex имеет большие overhead на переключение контекста, Spinlock — минимальные, но загружает ядро ЦП на 100%.
  • Использование: Mutex для операций > ~1-2 мкс, Spinlock — для сверхкоротких операций.

Пример реализации простого Spinlock на C++ с использованием std::atomic_flag:

#include <atomic>

class SpinLock {
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            // Подсказка процессору для активного ожидания (может снизить энергопотребление)
            __builtin_ia32_pause(); // Для x86 / __asm__ volatile("pause" ::: "memory");
        }
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

// Использование
SpinLock slock;
slock.lock();
// Критическая секция
slock.unlock();

На практике в C++ чаще используют std::mutex (реализация которого внутри может кратковременно использовать спин-луп) или std::atomic для lock-free алгоритмов.