Ответ
SpinLock (блокировка с активным ожиданием) — это низкоуровневый примитив синхронизации, в котором поток, не сумевший захватить блокировку, не переходит в состояние ожидания ядра ОС, а продолжает активно опрашивать ("крутиться" в цикле) флаг доступности.
Когда использовать:
- Короткие критические секции (несколько десятков или сотен инструкций).
- Минимизация накладных расходов на переключение контекста, когда время ожидания предположительно мало.
- В системном программировании, при реализации собственных примитивов или в ядре ОС.
Недостатки:
- Потребление процессорного времени: Поток в цикле ожидания занимает ядро ЦП (busy-waiting).
- Неэффективность: При длительном ожидании бесполезно тратит процессорные такты.
Реализация на C++ с использованием std::atomic_flag:
#include <atomic>
class SpinLock {
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
public:
void lock() {
// test_and_set() атомарно устанавливает флаг в true и возвращает предыдущее значение.
// Цикл продолжается, пока предыдущее значение было true (заблокировано).
while (lock_flag.test_and_set(std::memory_order_acquire)) {
// На многих архитекциях полезно дать подсказку процессору,
// что это цикл ожидания (может снизить энергопотребление/конфликт шины).
// __builtin_ia32_pause(); // Для x86
}
}
void unlock() {
// Атомарно сбрасываем флаг, разрешая другим потокам захватить блокировку.
lock_flag.clear(std::memory_order_release);
}
};
// Использование
SpinLock my_lock;
int shared_data = 0;
void thread_function() {
my_lock.lock();
++shared_data; // Критическая секция
my_lock.unlock();
}
На практике в пользовательском коде чаще используют std::mutex, который эффективнее для блокировок средней и большой длительности, так как передаёт управление планировщику ОС.
Ответ 18+ 🔞
А, SpinLock, говоришь? Ну это ж классика, ебать мои старые костыли! Представь себе: один поток влез в твои общие данные, а другой стоит под дверью и не просто ждёт, а тупо долбится лбом в косяк, пока первый не выйдет. Вот это и есть активное ожидание, или busy-waiting, если по-умному.
Так когда же эту штуку впиндюривать?
- Когда твоя критическая секция — короткая, как шаг пигалицы. Буквально пару сотен операций. Поток управится быстрее, чем ОС успеет его усыпить и разбудить.
- Когда тебе овердохуища важна скорость, а не вежливость. Чтобы не платить за переключение контекста, если ждать-то всего микросекунды.
- В ядре системы или когда ты пишешь свои, ещё более хитрые, примитивы синхронизации. Там это обычное дело.
А где подвох?
- Жрёт процессорное время, как не в себя. Пока один крутится в цикле, ядро ЦП у него занято на все сто. Это как если бы охранник на проходной вместо того чтобы сидеть на стуле, непрерывно приседал, ожидая пропуск.
- Полная неэффективность на длинных дистанциях. Если поток надолго засел внутри, то остальные будут просто жечь электричество впустую, терпения ноль ебать.
Вот, смотри, как это выглядит в коде на C++ с std::atomic_flag:
#include <atomic>
class SpinLock {
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
public:
void lock() {
// test_and_set() — атомарно ставит флаг в true и возвращает, что было ДО этого.
// Крутимся, пока кто-то уже не поставил true (заблокировал).
while (lock_flag.test_and_set(std::memory_order_acquire)) {
// Тут можно дать процессору намёк, что мы просто ждём.
// Типа, расслабься, не гони так шину. Для x86 это _mm_pause().
// __builtin_ia32_pause();
}
}
void unlock() {
// Просто сбрасываем флажок. Всё, проезд свободен.
lock_flag.clear(std::memory_order_release);
}
};
// Использование
SpinLock my_lock;
int shared_data = 0;
void thread_function() {
my_lock.lock();
++shared_data; // Делаем что-то важное в одиночестве
my_lock.unlock();
}
А в жизни-то что? Если ты не пишешь ядро или супер-оптимизированную библиотеку, то забей хуй и юзай обычный std::mutex. Он умный: если не получается зайти, поток засыпает, не жрёт цпу, и ОС будит его, когда надо. Для всего, что длиннее пары тактов, это правильный путь. Не изобретай велосипед, где можно проехать на трамвае.