В чем разница между Mutex и Spinlock в 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 алгоритмов.

Ответ 18+ 🔞

Давай разберем эту тему, чтобы не было как в том анекдоте, где один чувак пытается открыть додик, а другой его уже изнутри на спинлоке держит. Полный ёперный театр.

Представь, что у тебя есть один унитаз на весь офис — это общий ресурс. Мьютекс — это как взять талончик и спокойно присесть на диванчик с телефоном, пока предыдущий посетитель делает свои дела. Ты не тратишь силы, просто ждешь своей очереди. Потом тебя позовут. Это эффективно, если там кто-то надолго засел, с планшетом и кофе. Твои овердохуища накладных расходов — это переключение контекста, усыпление и пробуждение потока. Зато процессор свободен.

А теперь спинлок. Это когда ты не берешь талончик, а стоишь вплотную к двери, дергаешь ручку каждую миллисекунду и орешь: «Сколько можно там сидеть, я ща обоссусь!». Ты не спишь, ты в активном ожидании, и твое ядро процессора загружено на все сто, как будто ты что-то делаешь, хотя по факту просто бздишь на месте. Но! Если чувак внутри только пукнуть зашел и сразу выйдет, то твои затраты на дерганье ручки будут меньше, чем беготня за талончиком и обратно.

Ключевые отличия, если по-простому:

  • Занято? Mutex: «Окей, посплю». Spinlock: «Нет, бля, буду дёргать, пока не откроют!».
  • Цена вопроса: Mutex — дорогое переключение контекста. Spinlock — дешёвый цикл, но процессор жрёт как печь.
  • Когда что юзать: Mutex — когда операция в критической секции долгая (больше пары микросекунд). Spinlock — только для суперкоротких действий, буквально пару инструкций, где усыплять поток нахуя не нужно.

Вот, смотри, пиздопроебибна реализация спинлока на atomic_flag. Главное — не забыть паузу для процессора, а то он совсем с ума сойдёт от такого цикла.

#include <atomic>

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

// Использование
SpinLock slock;
slock.lock();
// Твоя сверхбыстрая критическая секция. Только зайди-выйди.
slock.unlock();

На деле, конечно, в C++ чаще хуй с горы — берут std::mutex и не парятся. А умные его реализации внутри сами решают, когда немного покрутиться в спинлупе, а когда уже усыплять. Но понимать разницу — это, чувак, основа, чтобы не написать какую-нибудь хитрую жопу, которая будет бензин хавать просто так.