Спин-блокировка — это блокировка, а значит, механизм взаимного исключения (строго 1 к 1). Он работает путем многократного запроса и/или изменения области памяти, обычно атомарным образом. Это означает, что получение спин-блокировки является «занятой» операцией, которая может сжигать такты процессора в течение длительного времени (возможно, навсегда!), в то время как фактически «ничего не достигается». Переключение контекста имеет накладные расходы, эквивалентные нескольким сотням (или, может быть, тысячам) циклов, поэтому, если блокировку можно получить, прожигая несколько циклов вращения, в целом это может быть более эффективным. Кроме того, для приложений реального времени может быть неприемлемо блокировать и ждать, пока планировщик вернется к ним в какое-то отдаленное время в будущем.
#include <boost/atomic.hpp> class spinlock { private: typedef enum {Locked, Unlocked} LockState; boost::atomic<LockState> state_; public: spinlock() : state_(Unlocked) {} void lock() { while (state_.exchange(Locked, boost::memory_order_acquire) == Locked) { /* busy-wait */ } } void unlock() { state_.store(Unlocked, boost::memory_order_release); } }; // Usage spinlock s; s.lock(); // access data structure here s.unlock();
Семафор, напротив, либо вообще не вращается, либо вращается только в течение очень короткого времени (как оптимизация, позволяющая избежать накладных расходов на системные вызовы). Если семафор не может быть получен, он блокируется, уступая процессорное время другому потоку, готовому к запуску. Это, конечно, может означать, что пройдет несколько миллисекунд, прежде чем ваш поток будет снова запланирован, но если это не проблема (обычно это не так), то это может быть очень эффективным подходом, щадящим ЦП.
Хорошо спроектированная система обычно имеет низкую перегрузку или ее отсутствие (это означает, что не все потоки пытаются получить блокировку в одно и то же время). Например, обычно не нужно писать код, который получает блокировку, затем загружает полмегабайта сжатых данных из сети, декодирует и анализирует данные и, наконец, изменяет общую ссылку (добавляет данные к контейнеру и т. д.), прежде чем снять блокировку. Вместо этого можно получить блокировку только с целью доступа к общему ресурсу.
Поскольку это означает, что за пределами критической секции выполняется значительно больше работы, чем внутри нее, естественно, вероятность поток, находящийся внутри критической секции, относительно низок, и, таким образом, несколько потоков борются за блокировку одновременно. Конечно, время от времени два потока будут пытаться получить блокировку одновременно (если бы это не могло случиться, вам не нужна была бы блокировка!), но это скорее исключение, чем правила в «здоровой» системе.
В таком случае спин-блокировка значительно превосходит семафор, потому что, если нет перегрузки блокировки, накладные расходы на получение спин-блокировки составляют всего дюжину циклов по сравнению с сотнями/тысячами циклов для переключения контекста или 10–20 миллионов циклов за потерю остатка кванта времени.
С другой стороны, при высокой перегрузке или если блокировка удерживается в течение длительного времени (иногда вы просто ничего не можете поделать!), спин-блокировка будет сжигать безумное количество циклов ЦП, чтобы ничего не добиться.
Семафор. (или мьютекс) в этом случае является гораздо лучшим выбором, так как он позволяет другому потоку выполнять полезные задачи в это время. Или, если никакой другой поток не может сделать что-то полезное, он позволяет операционной системе снизить нагрузку на ЦП и уменьшить тепло/сберегать энергию.
Источник: https://stackoverflow.com/questions/195853/spinlock-versus-semaphore