Articles

Spinlock vs Semafor

Spinlock jest blokadą, a więc mechanizmem wzajemnego wykluczania (ściśle 1 do 1). Działa poprzez wielokrotne odpytywanie i/lub modyfikowanie miejsca w pamięci, zazwyczaj w sposób atomowy. Oznacza to, że nabycie spinlocka jest „zajętą” operacją, która prawdopodobnie spala cykle CPU przez długi czas (może na zawsze!), podczas gdy efektywnie osiąga „nic”.
Główną zachętą do takiego podejścia jest fakt, że przełącznik kontekstu ma narzut równoważny obracaniu się kilkaset (a może tysiąc) razy, więc jeśli blokada może być nabyta przez spalenie kilku cykli obracania się, może to być ogólnie bardzo dobrze bardziej wydajne. Ponadto, dla aplikacji czasu rzeczywistego może być nie do przyjęcia, aby blokować i czekać na harmonogram, aby wrócić do nich w jakimś odległym czasie w przyszłości.

#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, w przeciwieństwie do tego, albo nie obraca się w ogóle, lub tylko obraca się przez bardzo krótki czas (jako optymalizacja, aby uniknąć syscall overhead). Jeśli semafor nie może zostać przejęty, blokuje się, oddając czas procesora innemu wątkowi, który jest gotowy do działania. Może to oczywiście oznaczać, że kilka milisekund minie, zanim twój wątek zostanie ponownie zaplanowany, ale jeśli nie jest to problem (zazwyczaj nie jest), to może to być bardzo wydajne, konserwatywne dla procesora podejście.

Dobrze zaprojektowany system zwykle ma niskie lub żadne zatłoczenie (oznacza to, że nie wszystkie wątki próbują zdobyć zamek dokładnie w tym samym czasie). Na przykład, zazwyczaj nie pisze się kodu, który uzyskuje blokadę, następnie ładuje pół megabajta danych skompresowanych zipem z sieci, dekoduje i przetwarza dane, a na koniec modyfikuje współdzielone odniesienie (dołącza dane do kontenera, itp.) przed zwolnieniem blokady. Zamiast tego można by nabyć blokadę tylko w celu uzyskania dostępu do współdzielonego zasobu.
Ponieważ oznacza to, że jest znacznie więcej pracy poza sekcją krytyczną niż wewnątrz niej, naturalnie prawdopodobieństwo, że wątek znajduje się wewnątrz sekcji krytycznej jest stosunkowo niskie, a zatem niewiele wątków walczy o zamek w tym samym czasie. Oczywiście co jakiś czas dwa wątki będą próbowały zdobyć blokadę w tym samym czasie (gdyby to się nie mogło zdarzyć, nie potrzebowalibyśmy blokady!), ale jest to raczej wyjątek niż reguła w „zdrowym” systemie.

W takim przypadku spinlock znacznie przewyższa semafor, ponieważ jeśli nie ma przeciążenia blokady, narzut nabycia spinlocka to zaledwie kilkanaście cykli w porównaniu do setek/tysięcy cykli na przełączenie kontekstu lub 10-20 milionów cykli na utratę pozostałej części wycinka czasu.

Z drugiej strony, biorąc pod uwagę duże zatłoczenie, lub jeśli blokada jest utrzymywana przez długi czas (czasami po prostu nie możesz pomóc!), spinlock spali szalone ilości cykli procesora za osiągnięcie niczego.
Semafor (lub muteks) jest znacznie lepszym wyborem w tym przypadku, ponieważ pozwala innemu wątkowi uruchomić użyteczne zadania w tym czasie. Lub, jeśli żaden inny wątek nie ma nic użytecznego do zrobienia, pozwala to systemowi operacyjnemu dławić procesor i zmniejszać ciepło / oszczędzać energię.

.