回答:
スピンロックは、共有リソースが2つ以上のプロセスによって同時に変更されるのを防ぐ方法です。リソースを変更しようとする最初のプロセスは、ロックを「取得」し、その方法で続行し、リソースで必要なことを行います。その後ロックを取得しようとする他のプロセスは停止します。それらは、ロックが最初のプロセスによって解放されるのを待機している「スピンインプレース」と言われています。
Linuxカーネルは、特定の周辺機器にデータを送信するときなど、多くのことにスピンロックを使用します。ほとんどのハードウェア周辺機器は、複数の状態の同時更新を処理するようには設計されていません。2つの異なる変更を行う必要がある場合、1つは厳密にもう1つに従う必要があり、重複することはできません。スピンロックは必要な保護を提供し、変更が一度に1つずつ行われるようにします。
スピンロックは、スレッドのCPUコアが他の作業を行うことをブロックするため、問題です。Linuxカーネルは、その下で実行されるユーザー空間プログラムにマルチタスクサービスを提供しますが、その汎用マルチタスク機能はカーネルコードに拡張されません。
この状況は変化しており、Linuxのほとんどの存在のためです。Linux 2.0まで、カーネルはほぼ純粋にシングルタスクプログラムでした。CPUがカーネルコードを実行しているときは常に、1つのCPUコアのみが使用されました。これは、Big Kernel Lock(BKL )。Linux 2.2以降、BKLはゆっくりと多くの独立したロックに分割され、それぞれがより集中したクラスのリソースを保護します。現在、カーネル2.6にはBKLがまだ存在していますが、BKLは、よりきめ細かいロックに簡単に移動できない本当に古いコードでのみ使用されています。マルチコアボックスでは、すべてのCPUが有用なカーネルコードを実行できるようになりました。
Linuxカーネルには一般的なマルチタスク機能がないため、BKLを分割するユーティリティには制限があります。カーネルスピンロックでスピンしているCPUコアがブロックされると、ロックが解除されるまで他のことをするために再タスクすることはできません。ロックが解除されるまで、ただ座って回転します。
すべてのコアが常に単一のスピンロックを待機しているようなワークロードの場合、スピンロックはモンスターの16コアボックスを効果的にシングルコアボックスに変えることができます。これは、Linuxカーネルのスケーラビリティの主な制限です。CPUコアを2から4に倍増すると、おそらくLinuxボックスの速度がほぼ2倍になりますが、ほとんどのワークロードでは、16から32に倍速にならないでしょう。
スピンロックとは、プロセスがロックを解除するために継続的にポーリングすることです。プロセスが(通常)不必要にサイクルを消費しているため、悪いと考えられます。Linux固有ではなく、一般的なプログラミングパターンです。そして、それは一般に悪い習慣と見なされますが、実際には正しい解決策です。スケジューラを使用するコストが、スピンロックの持続が予想される数サイクルのコストよりも高い場合があります(CPUサイクルの観点から)。
スピンロックの例:
#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
sleep 1
done
#do stuff
多くの場合、スピンロックを回避する方法があります。この特定の例には、inotifywaitと呼ばれるLinuxツールがあります(通常、デフォルトではインストールされません)。Cで作成されている場合は、Linuxが提供するinotify APIを使用するだけです。
inotifywaitを使用した同じ例は、スピンロックなしで同じことを実現する方法を示しています。
#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
スレッドがロックを取得しようとすると、失敗すると次の3つのことが起こります。試行とブロック、試行と続行、スリープへ移行して、イベントが発生したときにOSにウェイクアップするように伝えます。
これで、試行と継続は、試行とブロックよりもはるかに少ない時間で済みます。しばらくの間、「試行して続行」にはi単位の時間がかかり、「試行してブロック」には100時間がかかるとします。
ここで、スレッドが平均してロックを保持するのに4単位の時間がかかると仮定します。100単位を待つのはもったいない。そのため、代わりに「try and continue」のループを作成します。4回目の試行では、通常ロックを取得します。これはスピンロックです。これは、スレッドがロックを取得するまで所定の位置で回転し続けるためです。
追加の安全対策は、ループの実行回数を制限することです。したがって、この例ではforループをたとえば6回実行し、失敗した場合は「試行してブロック」します。
スレッドが常に約200ユニットのロックを保持することがわかっている場合、試行と継続のたびにコンピューターの時間を無駄にしています。
そのため、最終的に、スピンロックは非常に効率的または無駄になります。ロックを保持する「典型的な」時間が「試行してブロック」するのにかかる時間よりも長い場合は無駄です。通常、ロックを保持する時間が「試行してブロックする」時間よりもはるかに短い場合に効率的です。
追伸:スレッドについて読む本は、まだ見つけられるなら「スレッド入門書」です。
ロックが同期するように二つ以上のタスク(プロセス、スレッド)のための方法です。具体的には、両方のタスクが一度に1つのタスクのみが使用できるリソースに断続的にアクセスする必要がある場合、タスクがリソースを同時に使用しないように調整する方法です。リソースにアクセスするには、タスクは次の手順を実行する必要があります。
take the lock
use the resource
release the lock
別のタスクがすでにロックを取得している場合、ロックを取得することはできません。(ロックを物理的なトークンオブジェクトと考えてください。オブジェクトが引き出しにあるか、誰かが手に持っています。オブジェクトを保持している人だけがリソースにアクセスできます。)他の誰もロックしていないので、それを取得します」。
高レベルの観点から見ると、ロックを実装するには、スピンロックと条件の2つの主要な方法があります。スピンロック誰もまでちょうど「スピニング」(つまり、ループで何もしない)ロック手段を取って、他のロックを持っています。条件により、タスクがロックを取得しようとしたが、別のタスクがロックを保持しているためにブロックされた場合、新人は待機キューに入ります。リリース操作は、ロックが使用可能になったことを待機タスクに通知します。
(これらの説明は、ロックを実装するのに十分ではありません。原子性については何も述べていないためです。しかし、原子性はここでは重要ではありません。)
スピンロックは明らかに無駄です。待機中のタスクはロックがかかっているかどうかをチェックし続けます。それで、なぜ、そして、いつ、それは使われますか?スピンロックは、ロックが保持されていない場合に入手するのが非常に安価なことがよくあります。これにより、ロックが保持される機会が少ない場合に魅力的です。さらに、スピンロックは、ロックの取得に時間がかかると予想されない場合にのみ実行可能です。そのため、スピンロックは非常に短い時間保持される状況で使用される傾向があるため、ほとんどの試行は最初の試行で成功し、待機が必要な試行は長く待機しません。
Linuxデバイスドライバーの第5章には、Linuxカーネルのスピンロックおよびその他の同時実行メカニズムに関する適切な説明があります。
synchronized
スピンロックによって実装されることを疑います:synchronized
ブロックは非常に長い間実行できます。synchronized
は、特定の場合にロックを使いやすくするための言語構成体であり、より大きな同期プリミティブを構築するためのプリミティブではありません。
スピンロックは、スケジューラを無効にすることで動作するロックであり、ロックが取得される特定のコアで割り込み(irqsaveバリアント)を行う可能性があります。ミューテックスとは異なり、スピンロックが保持されている間はスレッドのみが実行できるようにスケジューリングを無効にします。ミューテックスを使用すると、他の優先度の高いスレッドを保留中にスケジュールできますが、保護されたセクションを同時に実行することはできません。スピンロックはマルチタスクを無効にするため、スピンロックを取得してから、ミューテックスを取得しようとする他のコードを呼び出すことはできません。スピンロックセクション内のコードは決してスリープしてはいけません(通常、ロックされたミューテックスまたは空のセマフォに遭遇すると、コードはスリープします)。
ミューテックスとのもう1つの違いは、スレッドは通常、ミューテックスのキューにあるため、その下のミューテックスにはキューがあることです。一方、spinlockは、必要な場合でも他のスレッドが実行されないようにします。したがって、ファイルの外部でスリープしないかどうかわからない関数を呼び出すときは、スピンロックを保持してはいけません。
スピンロックを割り込みと共有する場合、irqsaveバリアントを使用する必要があります。これは、スケジューラを無効にするだけでなく、割り込みも無効にします。それは理にかなっていますか?Spinlockは、他に何も実行されないようにすることで機能します。割り込みを実行したくない場合は、割り込みを無効にし、クリティカルセクションに安全に進みます。
マルチコアマシンでは、スピンロックは実際にスピンし、ロックを保持している別のコアがロックを解除するのを待ちます。このスピンはマルチコアマシンでのみ発生します。シングルコアマシンでは発生しないためです(スピンロックを保持して続行するか、ロックが解除されるまで実行しないかのいずれかです)。
スピンロックは、理にかなっている場合は無駄ではありません。非常に小さなクリティカルセクションの場合、重要な作業を完了するために数マイクロ秒間スケジューラーを一時停止するのに比べて、mutexタスクキューを割り当てることは無駄です。ioオペレーション(スリープ状態になる可能性がある)でスリープまたはロックを保持する必要がある場合は、ミューテックスを使用します。確かにスピンロックを決してロックせず、割り込み内でそれを解放しようとします。これは機能しますが、while(flagnotset)のarduino crapのようになります。そのような場合は、セマフォを使用します。
メモリトランザクションのブロックの単純な相互排除が必要な場合は、スピンロックを取得します。ミューテックスロックの直前に複数のスレッドを停止し、ミューテックスが解放され、同じスレッドでロックおよびリリースするときに優先順位が最も高いスレッドを選択する場合は、ミューテックスを取得します。あるスレッドまたは割り込みでポストし、別のスレッドで取得する場合は、セマフォを取得します。相互排除を保証するためのわずかに異なる3つの方法であり、わずかに異なる目的に使用されます。