スピン ロックは、共有リソースが 2 つ以上のプロセスによって同時に変更されないように保護する方法です。リソースを変更しようとする最初のプロセスは、ロックを「取得」し、リソースに対して必要なことを行いながら、途中で続行します。その後ロックを取得しようとする他のプロセスはすべて停止します。それらは、最初のプロセスによってロックが解放されるのを待って「その場でスピン」すると言われているため、スピンロックと呼ばれています。
Linux カーネルは、特定の周辺機器にデータを送信するときなど、多くのことにスピン ロックを使用します。ほとんどのハードウェア周辺機器は、複数の同時状態更新を処理するようには設計されていません。 2 つの異なる変更が発生する必要がある場合は、一方が他方に厳密に従う必要があり、重複することはできません。スピン ロックは必要な保護を提供し、一度に 1 つずつ変更が行われるようにします。
スピン ロックは、そのスレッドの CPU コアが他の作業を行うのをブロックするため、問題になります。 Linux カーネルはその下で実行されているユーザー空間プログラムにマルチタスク サービスを提供しますが、その汎用マルチタスク機能はカーネル コードには拡張されません。
この状況は変化しつつあり、Linux が存在するほとんどの期間にわたって変化しています。 Linux 2.0 までは、カーネルはほぼ純粋にシングルタスク プログラムでした。CPU がカーネル コードを実行しているときは常に、1 つの CPU コアのみが使用されていました。これは、ビッグ カーネル ロック (BKL) と呼ばれるすべての共有リソースを保護する単一のスピン ロックがあったためです。 )。 Linux 2.2 から、BKL はゆっくりと多くの独立したロックに分割され、それぞれがより集中したリソース クラスを保護します。現在、カーネル 2.6 では BKL はまだ存在していますが、より細かいロックに簡単に移動できない、本当に古いコードでのみ使用されています。マルチコア ボックスで、すべての CPU が有用なカーネル コードを実行することが可能になりました。
Linux カーネルには一般的なマルチタスク機能がないため、BKL を分割するユーティリティには限界があります。カーネル スピン ロックで CPU コアのスピンがブロックされた場合、ロックが解除されるまで別の処理を実行するために再タスクすることはできません。ロックが解除されるまで、ただ座って回転します。
ワークロードが、すべてのコアが常に 1 つのスピン ロックを待機するようなものである場合、スピン ロックはモンスター 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 をウェイクアップするように指示する。 P>
トライ アンド コンティニューは、トライ アンド ブロックよりもはるかに短い時間で済みます。さしあたって、「トライ アンド コンティニュー」には 1 単位の時間がかかり、「トライ アンド ブロック」には 100 時間がかかるとしましょう。
ここで、スレッドがロックを保持するのに平均で 4 単位の時間がかかると仮定します。 100単位待つのはもったいない。代わりに、「試行して続行」のループを記述します。 4 回目の試行で、通常はロックを取得します。これはスピン ロックです。スレッドがロックを取得するまでその場で回転し続けるため、そう呼ばれます。
追加の安全対策は、ループの実行回数を制限することです。たとえば、for ループを 6 回実行し、失敗した場合は「試行してブロック」します。
スレッドが、たとえば 200 単位のロックを常に保持することがわかっている場合、試行と続行のたびにコンピューターの時間を無駄にしていることになります。
したがって、最終的に、スピンロックは非常に効率的または無駄になる可能性があります。ロックを保持するための「典型的な」時間が「試行してブロックする」のにかかる時間よりも長い場合、無駄です。ロックを保持する通常の時間が、「試行してブロックする」時間よりもはるかに短い場合に効率的です。
Ps:スレッドについて読むべき本は、「A Thread Primer」です。まだ見つけられる場合は。