Articles

Spinlock vs Semaphore

スピンロックはロックであり、したがって相互排除(厳密には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);
}
};// Usagespinlock s;
s.lock();
// access data structure here
s.unlock();

対照的に、セマフォはまったく回転しないか、非常に短い時間だけ回転します (syscall オーバーヘッドを回避する最適化として)。 セマフォが取得できない場合、ブロックされ、実行可能な別のスレッドにCPU時間を譲ります。 これはもちろん、スレッドが再びスケジュールされるまでに数ミリ秒経過することを意味するかもしれませんが、これが問題でなければ (通常はそうではありません)、非常に効率的で CPU を節約するアプローチになります。

よく設計されたシステムは、通常混雑が少ないか全くありません (これは、すべてのスレッドがまったく同時にロックを取得しようとしないことを意味します)。 たとえば、通常、ロックを取得し、ネットワークから半分のメガバイトの zip 圧縮データをロードし、データをデコードおよび解析し、最後にロックを解放する前に共有参照を変更する (コンテナーにデータを追加する、など) コードは記述しないでしょう。 その代わり、共有リソースにアクセスする目的でのみロックを取得します。
これは、クリティカル セクションの内側よりも外側の方がかなり多くの作業があることを意味するので、当然ながら、あるスレッドがクリティカル セクション内にいる可能性は比較的低く、同時にロックを争奪するスレッドはほとんど存在しないことになります。 もちろん、たまに2つのスレッドが同時にロックを取得しようとすることはありますが(これが起こらなければロックは必要ありません!)、これは「健全な」システムにおけるルールというよりもむしろ例外的なことなのです。

このような場合、スピンロックはセマフォを大きく上回ります。なぜなら、ロックの混雑がなければ、スピンロックを取得するオーバーヘッドは、コンテキスト スイッチの数百/数千サイクルやタイムスライスの残りを失う 1,000 万~ 2,000 万サイクルと比較して、わずか数十サイクルだからです。
この場合、セマフォ (またはミューテックス) がはるかに良い選択となります。 または、他のスレッドが何か有用なことを行っていない場合、オペレーティング システムが CPU をスロットルダウンし、熱を削減/エネルギーを節約することを可能にします。