← Назад к вопросам

Что такое spinlock? Когда его стоит использовать вместо мьютекса?

2.0 Middle🔥 61 комментариев
#Linux и операционные системы#Многопоточность и синхронизация#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Spinlock (Спин-блокировка)

Определение

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

В отличие от мьютекса, спинлок не переводит поток в сон, а держит его в цикле проверки.

Концепция

Мьютекс (спит):

Поток 1: Захватил мьютекс → работа → освободил
Поток 2: Пытается захватить → СПИТ (context switch) → просыпается → работа
ОС:      Может работать с другими потоками

Spinlock (вращается):

Поток 1: Захватил spinlock → работа → освободил
Поток 2: Пытается захватить → ВРАЩАЕТСЯ в цикле (context switch НЕ происходит)
ОС:      Поток 2 занимает CPU 100%

Реализация spinlock

Базовая реализация

#include <atomic>
#include <thread>
using namespace std;

class SimpleSpinlock {
private:
    atomic<bool> locked{false};

public:
    void lock() {
        // Вращаемся в цикле, пока не захватим
        while (locked.exchange(true, memory_order_acquire)) {
            // Занято, продолжаем ждать
        }
    }

    void unlock() {
        locked.store(false, memory_order_release);
    }
};

int main() {
    SimpleSpinlock lock;
    int shared = 0;

    auto worker = [&]() {
        for (int i = 0; i < 1000; ++i) {
            lock.lock();
            shared++;
            lock.unlock();
        }
    };

    thread t1(worker);
    thread t2(worker);

    t1.join();
    t2.join();

    cout << "Result: " << shared << endl;  // 2000
    return 0;
}

Оптимизированный spinlock с pause

Без оптимизации spinlock буквально сжигает CPU. Добавим pause инструкцию (x86-64):

#include <atomic>
#include <thread>
#include <immintrin.h>  // Для _mm_pause()
using namespace std;

class OptimizedSpinlock {
private:
    atomic<bool> locked{false};

public:
    void lock() {
        while (locked.exchange(true, memory_order_acquire)) {
            _mm_pause();  // Подсказка CPU: спец инструкция для ожидания
        }
    }

    void unlock() {
        locked.store(false, memory_order_release);
    }
};

Spinlock с exponential backoff

#include <atomic>
#include <thread>
using namespace std;

class BackoffSpinlock {
private:
    atomic<bool> locked{false};

public:
    void lock() {
        int spins = 0;
        const int MAX_SPINS = 32;

        while (locked.exchange(true, memory_order_acquire)) {
            if (spins < MAX_SPINS) {
                // Малое ожидание: спинимся
                for (int i = 0; i < (1 << spins); ++i) {
                    __asm__("pause");  // x86 инструкция
                }
                ++spins;
            } else {
                // После MAX_SPINS даём другому потоку время
                this_thread::yield();  // Выход из цикла, но не спим
            }
        }
    }

    void unlock() {
        locked.store(false, memory_order_release);
    }
};

Сравнение: Spinlock vs Мьютекс

ХарактеристикаSpinlockМьютекс
ОжиданиеСпинится в циклеСпит (context switch)
CPU использование100% (активное ожидание)Минимальное (спит)
Контекст-переключениеНетДа (дорого!)
Время захватаОчень быстроМедленнее (переключение)
Для коротких блокировокОтличный выборМожет быть неэффективен
Для длинных блокировокУжасен (сжигает CPU)Оптимален
Справедливость (fairness)Нет (голодание потоков)Да

Практические примеры

Пример 1: Сверхбыстрый счётчик

#include <atomic>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;

class Counter {
private:
    long count = 0;
    atomic<bool> lock{false};

public:
    void increment() {
        // Spinlock для счётчика (очень короткая критическая секция)
        while (lock.exchange(true, memory_order_acquire));
        count++;
        lock.store(false, memory_order_release);
    }

    long getCount() const { return count; }
};

int main() {
    Counter counter;
    vector<thread> threads;

    auto start = chrono::high_resolution_clock::now();

    for (int i = 0; i < 4; ++i) {
        threads.emplace_back([&]() {
            for (int j = 0; j < 1000000; ++j)
                counter.increment();
        });
    }

    for (auto& t : threads) t.join();

    auto end = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);

    cout << "Count: " << counter.getCount() << endl;
    cout << "Time: " << duration.count() << "ms" << endl;
    return 0;
}

Пример 2: Lock-free очередь со spinlock

#include <atomic>
#include <queue>
using namespace std;

template<typename T>
class SpinlockQueue {
private:
    queue<T> q;
    atomic<bool> spinlock{false};

public:
    void enqueue(const T& value) {
        while (spinlock.exchange(true, memory_order_acquire));
        q.push(value);
        spinlock.store(false, memory_order_release);
    }

    bool dequeue(T& value) {
        while (spinlock.exchange(true, memory_order_acquire));
        bool success = !q.empty();
        if (success) {
            value = q.front();
            q.pop();
        }
        spinlock.store(false, memory_order_release);
        return success;
    }
};

Когда использовать spinlock

Spinlock хорош для:

  1. Очень короткие критические секции (< 1 микросекунда)

    spinlock.lock();
    counter++;        // Одна операция
    spinlock.unlock();
    
  2. High-performance системы (финтех, торговля, HFT)

    • Каждая микросекунда считается
    • Предсказуемость важнее экономии CPU
  3. Real-time системы (RTOS)

    • Нужны гарантии по времени ответа
    • Context switch недопустим
  4. Многоядерные системы (когда каждый поток на отдельном ядре)

    • Context switch дорогой
    • Spilling вероятен: другой поток скоро освободит блокировку

Когда НЕ использовать spinlock

Мьютекс лучше для:

  1. Длительные операции в критической секции

    spinlock.lock();
    sleep(1);         // Плохо! Spinlock вращается целую секунду
    spinlock.unlock();
    
  2. Однопоточные приложения — вечное спинирование!

  3. Неизвестна длительность блокировки

    • Может быть как коротко, так и долго
    • Мьютекс универсален
  4. Мало ядер или высокая контенция (много потоков борются за одну блокировку)

    • Context switch случится в любом случае
    • Спинирование только сжигает CPU зря

Анализ производительности

Сценарий 1: Низкая контенция, короткие блокировки

Крит. секция: 100ns (0.0001ms)
Время контекст-переключения: 1000ns (0.001ms)

Mutex: ~1000ns (переключение)       [ПЛОХО]
Spinlock: ~100ns (вращение)         [ХОРОШО]
Викторь: Spinlock на 10x быстрее!

Сценарий 2: Высокая контенция, длительные блокировки

Крит. секция: 10000ns (0.01ms)
Время контекст-переключения: 1000ns (0.001ms)
Количество конкурирующих потоков: 8

Mutex: ~1000ns + очередь ожидания  [ХОРОШО — спит]
Spinlock: ~80000ns (вращение)       [УЖАСНО — сжигает CPU]

C++ 17: std::atomic spin lock

В C++17 появился встроенный спинлок через std::atomic:

#include <atomic>
#include <thread>
using namespace std;

std::atomic_flag lock = ATOMIC_FLAG_INIT;

void critical_section() {
    // Спинлок на atomics
    while (lock.test_and_set(memory_order_acquire)) {
        // Спинируемся
    }
    // Критическая секция
    lock.clear(memory_order_release);
}

C++20 добавил atomic::wait() для более эффективного ожидания:

#include <atomic>
#include <thread>
using namespace std;

std::atomic<bool> lock{false};

void better_lock() {
    while (lock.exchange(true, memory_order_acquire)) {
        lock.wait(true, memory_order_relaxed);  // Более эффективно!
    }
}

Best practices

Правило большого пальца:

  • Если вы не уверены, используйте мьютекс
  • Если вы профилировали и знаете, что блокировка очень короткая, попробуйте spinlock
  • Для real-time и HFT систем spinlock часто обязателен

Итоговые рекомендации

Spinlock — это инструмент для экстремально низколатентных систем:

  1. Обменивает CPU на низкую задержку — спинирование вместо спанияания
  2. Отличен для коротких блокировок — когда spinirование быстрее контекст-переключения
  3. Опасен в неправильных ситуациях — может сжечь весь CPU
  4. Требует профилирования — не гадайте, измеряйте!
  5. HFT/финтех стандарт — основной выбор для финансовых систем

Мастерство выбора между spinlock и мьютексом — это признак опытного backend разработчика, понимающего оборудование и операционные системы.

Что такое spinlock? Когда его стоит использовать вместо мьютекса? | PrepBro