Spinlock vs Semafor
Un spinlock este o blocare și, prin urmare, un mecanism de excludere reciprocă (strict 1 la 1). Acesta funcționează prin interogarea și/sau modificarea repetată a unei locații de memorie, de obicei într-o manieră atomică. Acest lucru înseamnă că achiziționarea unui spinlock este o operațiune „ocupată” care, eventual, arde cicluri de procesor pentru o perioadă lungă de timp (poate pentru totdeauna!) în timp ce nu realizează efectiv „nimic”.
Principalul stimulent pentru o astfel de abordare este faptul că o comutare de context are un overhead echivalent cu rotirea de câteva sute (sau poate mii) de ori, astfel încât, dacă un blocaj poate fi achiziționat prin arderea câtorva cicluri de rotire, acest lucru poate fi, în general, foarte bine mai eficient. De asemenea, pentru aplicațiile în timp real s-ar putea să nu fie acceptabil să se blocheze și să aștepte ca planificatorul să revină la ele la un moment îndepărtat în viitor.
#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();
Un semafor, prin contrast, fie nu se rotește deloc, fie se rotește doar pentru un timp foarte scurt (ca o optimizare pentru a evita suprasolicitarea syscall). În cazul în care un semafor nu poate fi achiziționat, acesta se blochează, renunțând la timpul de procesare pentru un alt fir de execuție care este gata să ruleze. Acest lucru poate însemna, bineînțeles, că trec câteva milisecunde înainte ca firul dvs. să fie programat din nou, dar dacă acest lucru nu este o problemă (de obicei nu este), atunci poate fi o abordare foarte eficientă și conservatoare pentru CPU.
Un sistem bine conceput are, în mod normal, o congestie scăzută sau inexistentă (aceasta înseamnă că nu toate firele încearcă să achiziționeze blocajul exact în același timp). De exemplu, în mod normal, nu s-ar scrie un cod care să achiziționeze un blocaj, apoi să încarce jumătate de megabyte de date comprimate zip din rețea, să decodifice și să analizeze datele și, în cele din urmă, să modifice o referință partajată (să adauge date la un container etc.) înainte de a elibera blocajul. În schimb, cineva ar dobândi blocajul doar în scopul accesării resursei partajate.
Din moment ce acest lucru înseamnă că există considerabil mai multă muncă în afara secțiunii critice decât în interiorul acesteia, în mod natural, probabilitatea ca un fir să se afle în interiorul secțiunii critice este relativ scăzută și, prin urmare, puține fire concurează pentru blocaj în același timp. Bineînțeles că, din când în când, două fire vor încerca să obțină blocajul în același timp (dacă acest lucru nu s-ar putea întâmpla, nu ar fi nevoie de un blocaj!), dar aceasta este mai degrabă excepția decât regula într-un sistem „sănătos”.
Într-un astfel de caz, un spinlock depășește cu mult performanțele unui semafor, deoarece, dacă nu există congestie de blocare, cheltuielile de achiziție a spinlock-ului sunt de doar o duzină de cicluri, în comparație cu sutele/mii de cicluri pentru o comutare de context sau 10-20 de milioane de cicluri pentru pierderea restului unei porțiuni de timp.
Pe de altă parte, în condițiile unei congestii ridicate sau dacă blocajul este reținut pentru perioade lungi de timp (uneori nu te poți abține!), un spinlock va consuma cantități nebănuite de cicluri CPU pentru a nu obține nimic.
Un semafor (sau mutex) este o alegere mult mai bună în acest caz, deoarece permite unui alt fir de execuție să execute sarcini utile în acest timp. Sau, în cazul în care nici un alt fir nu are ceva util de făcut, permite sistemului de operare să micșoreze CPU și să reducă căldura/conserveze energia.
.