スピンロック

スピンロック（）とは、計算機科学におけるロックの一種で、スレッドがロックを獲得できるまで単純にループ（スピン）して定期的にロックをチェックしながら待つ方式。スレッドはその間有益な仕事を何もせずに動作し続けるため、これは一種のビジーウェイト状態を発生させる。獲得されたスピンロックは明示的に解放するまでそのまま確保されるが、実装によってはスレッドがブロック（スリープ）したときに自動的に解放される場合もある。
スレッドが短時間だけブロックされるならば、スピンロックは効率的であり、オペレーティングシステムのプロセススケジューリングのオーバーヘッドを防ぐことにもなる。このため、スピンロックはカーネル内でよく使われる。しかし、確保期間が長くなるとスピンロックは無駄が多くなり、他のスレッドの処理を妨害するだけでなく、再スケジューリングが必要になることもある。スレッドがロックを保持する時間が長くなればなるほど、ロックを持った状態でOSスケジューラによって割り込まれる可能性が高くなる。もしそうなると、ロックを保持しているスレッドがロックを解放することがないにもかかわらず、他のスレッドは（ロックを繰り返し獲得しようとして）スピンし続けてしまう。その結果、ロックを保持するスレッドがロックを解放するまで、他のスレッドは先に進むことができない（indefinite postponement状態になる）。これはシングルプロセッサシステムには特に当てはまる。というのも、他のスレッドが並行して動くことは決してないので、いったんスピンし始めるとタイムスライスを使い切るまでスピンし続けることになるのである。
スピンロックを正しく実装することは難しい。なぜなら、競合状態を避けるためにロックの同時アクセスの可能性を考慮しなければならないからである。一般に、これは特別なアセンブリ言語の命令（アトミックなテスト・アンド・セット操作など）を使う必要があり、高級言語やアトミック命令をサポートしていない言語では簡単には実装できない。アトミック命令をサポートしないアーキテクチャや、高級言語で実装しなければならない場合、ピーターソンのアルゴリズムといったアトミックでないロックアルゴリズムを用いることができるかもしれない。ただし、スピンロックより多くのメモリが必要になるかもしれないし、アウト・オブ・オーダー実行が許される場合は高級言語では実装できないかもしれない。
実装例.
以下の例は x86 アセンブリ言語によるスピンロックの実装である。Intel 80386互換プロセッサで動作する。
lock: # ロック変数。1 = ロック済み, 0 = ロックされていない
 dd 0
spin_lock:
 mov eax, 1 # EAX レジスタに 1 をセット
loop:
 xchg eax, [lock] # アトミックにEAXレジスタとロック変数の値を交換
 # ロックには常に 1 が格納され、以前の値が EAX レジスタに格納される。 
 test eax, eax # EAX 自身をチェック。EAX がゼロならば プロセッサのゼロフラグがセットされる。
 # EAX が 0 なら、ロックは解放状態から新たに確保されたとみなせる。
 # そうでなければ、EAX は 1 であり、ロックを獲得できていない。
 jnz loop # ゼロフラグがセットされていないときは XCHG 命令に戻る。
 # これはロックが既に他に獲得されていた場合で、スピンする必要がある。
 ret # ロックを獲得できたので、呼び出した関数へ戻る。
spin_unlock:
 mov eax, 0 # EAX レジスタに 0 をセット
 xchg eax, [lock] # アトミックに EAX レジスタとロック変数を交換
 ret # ロックを解放
最適化.
上記は（x86アセンブラを知っているならば）理解しやすい単純な実装で、全ての x86 アーキテクチャのCPUで動作する。しかし、非常に効果的な性能最適化手法がいくつか存在する。
x86 アーキテクチャでも比較的新しく実装されたものでは、ロックされた XCHG 命令の代わりにより高速なロックされていない MOV 命令で "spin_unlock" を実現できる。これは微妙なメモリの順序性によるもので、MOV命令自体は完全なメモリバリアではない。しかし、いくつかのプロセッサ（一部のサイリックスプロセッサ、バグを持っている一部バージョンのPentium Pro、初期のPentiumやi486によるSMPシステム）では、この方法は使えず、ロックが壊れてしまう。x86 アーキテクチャ以外では明示的なメモリバリア命令やアトミック命令が使われるか、特別な unlock 命令（IA-64など）があって必要なメモリの順序性を提供している。
CPU間のバストラフィックを低減するため、ロックを獲得できなかったときのループではロックの値が変化するまでメモリへの書き込みをすべきでない。MESIプロトコルなどのキャッシュプロトコルでは、これによってロックを含むキャッシュラインが "shared" 状態になるため、CPUはロックを待っている間全くバストラフィックを発生しない。この最適化はCPU毎のキャッシュを持つあらゆるアーキテクチャで有効である。
SSE2をサポートするx86系CPUでは、電力消費量を減らすために pause 命令を使用できる。上記の例のループの中に pause 命令を挿入すると、消費電力を抑えることができる（サポートしていないCPUでは rep nop と同等であり無視される）。これは「公平さ; fairness」を向上させることにもつながるが、公平さは他のCPUアーキテクチャでも大きな問題である。
代替方式.
スピンロックの第一の問題点はロックを獲得しようと待っている時間を浪費することである。これを避ける代替方式が2つ存在する。
いくつかのオペレーティングシステムは、まずスピンロックを使って、時間がかかるときはスレッドをサスペンドさせるという混合型の手法を用いる。Solarisは現在走行中のスレッドのリソースへのアクセスはスピンロックを使用し、走行中でないスレッドのリソースについてはスリープロックを使用する（シングルプロセッサシステムでは常に後者となる）。
外部リンク.
いずれも英文