Articles

Spinlock vs. semafor

Spinlock je zámek, a tedy mechanismus vzájemného vyloučení (striktně 1:1). Funguje tak, že se opakovaně dotazuje a/nebo modifikuje místo v paměti, obvykle atomickým způsobem. To znamená, že získání spinlocku je „zaneprázdněná“ operace, která možná spálí cykly procesoru na dlouhou dobu (možná navždy!), zatímco efektivně nedosáhne „ničeho“.
Hlavním podnětem pro takový přístup je skutečnost, že přepnutí kontextu má režii odpovídající několika set (nebo možná tisícům) otáčení, takže pokud lze zámek získat spálením několika cyklů otáčení, může to být celkově velmi dobře efektivnější. Pro aplikace pracující v reálném čase také nemusí být přijatelné blokovat a čekat, až se k nim plánovač vrátí v nějakém vzdáleném čase v budoucnosti.

#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);
}
};// Usagespinlock s;
s.lock();
// access data structure here
s.unlock();

Semafor se naproti tomu buď netočí vůbec, nebo se točí jen velmi krátkou dobu (jako optimalizace, aby se zabránilo režii syscall). Pokud semafor nelze získat, zablokuje se a přenechá čas procesoru jinému vláknu, které je připraveno ke spuštění. To samozřejmě může znamenat, že než bude vaše vlákno znovu naplánováno, uplyne několik milisekund, ale pokud to není problém (obvykle není), může to být velmi efektivní a úsporný přístup k procesoru.

Dobře navržený systém má obvykle nízké nebo žádné přetížení (to znamená, že ne všechna vlákna se snaží získat zámek přesně ve stejnou dobu). Obvykle se například nepíše kód, který získá zámek, pak načte půl megabajtu dat komprimovaných zipem ze sítě, dekóduje a analyzuje data a nakonec upraví sdílenou referenci (připojí data do kontejneru atd.), než zámek uvolní. Místo toho by člověk získal zámek pouze za účelem přístupu ke sdílenému prostředku.
Protože to znamená, že mimo kritickou sekci je podstatně více práce než uvnitř ní, je přirozeně pravděpodobnost, že se vlákno nachází uvnitř kritické sekce, relativně nízká, a proto se o zámek současně uchází jen málo vláken. Samozřejmě se občas stane, že se zámek pokusí získat dvě vlákna současně (kdyby se to nemohlo stát, nepotřebovali byste zámek!), ale to je ve „zdravém“ systému spíše výjimka než pravidlo.

V takovém případě spinlock výrazně překonává semafor, protože pokud nedochází k přetížení zámku, režie získání spinlocku je pouhý tucet cyklů ve srovnání se stovkami/tisíci cyklů při přepnutí kontextu nebo 10-20 miliony cyklů při ztrátě zbytku časového úseku.

Na druhou stranu, při vysokém přetížení nebo pokud je zámek držen po dlouhou dobu (někdy si prostě nemůžete pomoct!), spinlock spálí šílené množství cyklů procesoru pro dosažení ničeho.
Semafor (nebo mutex) je v tomto případě mnohem lepší volbou, protože umožňuje jinému vláknu provádět během této doby užitečné úlohy. Nebo, pokud žádné jiné vlákno nemá na práci něco užitečného, umožňuje operačnímu systému přiškrtit procesor a snížit zahřívání / šetřit energii.