反発量mutexはどのように動作しますか

3493 ワード

同時プログラミングには同期操作が必要です.同じ時点で1つ以上のスレッドでデータを処理することはできません.そうしないと、データ競合に遭遇します.最も一般的な方法は、重要なデータへのアクセス権をmutexに配置することです.mutexはもちろん無料ではありません.mutexは、私たちが作成したコードのパフォーマンスに深刻な影響を及ぼすことができます.正しく使用すると、mutexが導入したパフォーマンス支出に気づくのは難しい.しかし、誤って使用した場合、マルチスレッドの性能は単一スレッドに及ばない可能性があります.反発量mutexでは、最も基本的な形式はメモリの整数です.この整数はmutexの異なる状態に基づいて異なる値を持つことができる.mutexesについて議論するとき、ロックメカニズムについても議論する必要があります.メモリに格納されている整数は面白くありませんが、その周りの動作は面白いです.
反発量は、ロックとロック解除の2つの操作をサポートする必要があります.スレッドが反発量を使用する場合は、ロックを呼び出し、解放時にロックを解除する必要があります.いつでも、反発量にはロックが1つしかありません.ロックされたスレッドは、現在の反発量の所有者です.別のスレッドが制御権限を取得するには、最初のスレッドのロック解除を待たなければなりません.mutual exclusionはmutexの本意です.施錠操作は多種多様である.ほとんどの場合、鍵をかけることができるまで待ち続けます.そのため、ほとんどのロック操作はこのようなものです.他の場合、私たちはしばらく待ちたいかもしれません.あるいは、待ちたくないかもしれません.これに対して、ロックは通常1つの関数にすぎません.ロックを解除すると、別のスレッドがロックされます.
競合は、すでにロックされている反発量を競合と呼びます.良好な計画のプログラムの中で、競争が発生すべき回数は少ないはずだ.あなたのコードを設計しなければなりません.そのため、ほとんどの試みは反発量をロックする操作がブロックされません.競争を避けたい理由は2つあります.1つ目は,任意のスレッドがロック解放を待つと他のことはしないため,CPUを浪費する.もう1つの理由は、特に高性能コードが興味深いからです.現在のロック解除をロックする反発量は、競合する場合よりもコスト消費が小さい.なぜかを理解するには、反発量の働きのメカニズムを理解しなければならない.
前述したように、反発量のデータはメモリ内で整数である.最初は0で、鍵がかかっていないことを意味します.反発量をロックしたい場合は、0であるかどうかを確認し、1に値を付ける必要があります.そしてこの反発量はロックされ、ロックの所有者になります.このテクニックはtest-and-set命令が必ず原子操作であることにある.2つのスレッドが同時に0を読み、1を書くと、反発量が得られたと考えられます.CPUのサポートがなければ,ユーザ空間での反発量は実現できない.この操作は必ず別のスレッドに対して原子である必要がある.幸いなことに、CPUには「compare-and-set」または「test-and-set」という機能があります.この機能には整数のアドレスが必要です.すでに2つの値があります.比較値と設定値です.比較値が現在の整数と同じ場合は、新しい値に置き換えられます.そうでなければ、古い値が使用されます.Cスタイルのコードは、int compare_set( int * to_compare, int compare, int set );int mutex_value;int result = compare_set( &mutex_value, 0, 1 );if( !result ) { /* we got the lock */ }
この部分はもう少し詳しく紹介する必要があります.呼び出し者は,戻り値を観察することによって何が起こったかを判断する.この値が比較値と同じ場合、呼び出し元の設定は成功し、異なる場合、呼び出し元は失敗します.このセクションのコードがロックを解除すると、値が0に戻ります.これが私たちの反発量メカニズムです.
原子の増減機能も使えますが、linux futexを使うならこれがおすすめです.
What about waitingは次に厄介な部分に着いた.一つの角度から見ると、これは簡単で、もう一つの角度から見ると難しいです.上記のtest-and-setメカニズムでは、スレッド待ち値の機能は提供されません.CPUは上位層のスレッドやプロセスを理解しないため,待機機能を実現する場所ではない.オペレーティングシステムは必ずこの待機機能を提供しなければならない.
CPUを正しく待機させるために、呼び出し者はシステム呼び出しを経なければならない.オペレーティングシステムは、異なるスレッドを同期し、待機機能を提供する唯一の場所です.反発量を待つか、待機中の反発量を解放する必要がある場合は、オペレーティングシステムのシステム呼び出し以外に選択肢はありません.ほとんどのオペレーティングシステムには、反発量の原語が内蔵されています.場合によっては、それらは完全に成熟して利用可能な反発量を提供する.システム呼び出しが完全な反発量を提供できる場合、なぜユーザースペースにtest-and-setを書く必要があるのでしょうか.システム呼び出しのコストが大きいため、できるだけ避けるべきだ.
オペレーティングシステムによって処理方法が異なり、このような異なる傾向が続く可能性があります.linuxでは、futexを呼び出してmutexのような意味を提供します.競合がない場合は、ユーザースペースで呼び出しが解決されます.競合がある場合、呼び出しはオペレーティングシステムに渡され、より安全で、コストも高いモードに入ります.オペレーティングシステムは、プロセススケジューラの一部として待機操作の処理を担当します.
ここでさらに説明すると,競合がなければシステム呼び出しは行われない.競合がある場合、システム呼び出しは、ロックを獲得するスレッドをsleep queueに入れるために使用されます.mutexが解放されると、オペレーティングシステムもスリープキューから最初のスレッドを取り出して起動します.この場合も、単に反発量を初期値に戻すことでロックの解放を行うわけではない.システム呼び出しによってロックを渡すownershipです.一言で言えば、ロックの争いが発生すると、煩雑なシステム呼び出しが必要になります.これは、コンカレントプログラムを作成するのに見たくないことです.
futexはかなり柔軟な操作であり、mutexに加えて、旗語、バリア、読み書きロック、イベント信号などの様々なロックメカニズムを作成することができます.
The Costsはmutexの代価にかかわると面白いことに気づく.一番大切なのは待つ時間です.スレッドは、反発ロックを待つのにわずかな時間しかかかりません.頻繁に待つと、並列性が失われます.最悪の場合、多くのスレッドが反発量をロックしようとし、単独のスレッドよりもパフォーマンスが低下します.これはmutex自身の代価ではないが,並列プログラミングで考慮すべき問題である.mutex過負荷に関する問題にはtest-and-set操作とシステム呼び出しが含まれており、反発量を実現している.test-and-setは一般的に小さなオーバーヘッドであり、並列処理に非常に重要である.CPUのメーカーはtest-and-set命令を効率的に実行できるように努力します.もう一つの重要な命令,fence障壁を無視した.これは種々の高レベルの反発量において大量に用いられる.さらにtest-and-set命令よりもコストが高いが、ほとんどのコストはシステム呼び出しである.システム呼び出しはコンテキストの切り替えの代価をもたらすだけでなく、カーネルもコードをスケジューリングするのに時間がかかります.
私の公式アカウント「プロセッサーとAIチップ」に注目してください.