Linux スレッド セーフで一般的に使用されるロック メカニズム

からの転載: https://blog.csdn.net/qq_35423154/article/details/109259881


楽観ロック VS 悲観ロック

楽観的ロックと悲観的ロックは、その名前にちなんで名付けられています. それらの違いは、物事を行うための考え方です.

悲観ロック

悲観的ロックはより悲観的です.共有リソースは他のスレッドによって使用時に変更されると常に考えているため, スレッドの安全性の問題に簡単につながる可能性があります. したがって, 共有データにアクセスする前に, 共有データにアクセスしてブロックする前にロックする必要があります.他のスレッドによるアクセス

一般的な例は、データベース内の行ロック、テーブル ロック、読み取りロック、書き込みロックなどです。


楽観的ロック

楽観的ロックは悲観的ロックの反対であり、より楽観的です。複数のスレッドが共有リソースを同時に変更する可能性は低いと常に考えているため、3721 を無視して変更しましょう。

楽観的ロックは共有リソースを直接変更しますが、変更結果を更新する前に、この期間中に他のスレッドがリソースを変更したかどうかを確認し、そうでない場合は更新を送信し、そうであれば操作を放棄します。

楽観的ロックはプロセス全体でロックされないため、ロックフリー プログラミングとも呼ばれ、通常は CAS 操作 + バージョン番号メカニズムで実装されます


キャス

キャスメカニズム

CASは英語のCompare And Swapの略語、つまり比較と置換であり、まさにその核となるものです。
CAS メカニズムでは、メモリ アドレス V、古い期待値 A、新しい期待値 B の3 つの基本オペランドが使用されます。

変数を変更する必要がある場合、メモリ アドレス V が古い期待値と比較され、2 つが同じ場合は、古い期待値 A が新しい期待値 B に置き換えられます。異なる場合は、 V の値を古い期待値として使用し、上記の操作、つまり spin を繰り返します

以下はそれぞれ成功例と失敗例ですが
、このときメモリアドレスに格納されている値は9、スレッド1の古い期待値は9、新しい期待値は10なので、1を加算する必要があります。このとき、
ここに画像の説明を挿入
古い 期待値は V と同じで、B を V に交換し、
ここに画像の説明を挿入
修正は成功です。

次に、変更が失敗する状況を見てください

このとき、V の値は 9 で、スレッド 1 の古い期待値は 9 です。V の値を 10 に変更したいと考えています。
ここに画像の説明を挿入
変更を開始しようとすると、突然スレッドが最初にデータを更新し、 V の値は 14 になります
ここに画像の説明を挿入
このとき A の値は V とは異なるため、V の値を再取得し、新しい期待値を計算する必要があります
ここに画像の説明を挿入

この時点で両者は同じで交換完了、V=15

上記からわかるように、CAS は楽観的ロックであり、プログラムの同時実行性はそれほど深刻ではないと楽観的に信じているため、スレッドが更新を試行し続けることができます。


ABA の質問

いわゆる ABA 問題は、変数を AからB に変更し、次に B から A に変更することです。

銀行でお金を引き出しているとします.このとき、私の口座には1,000元があり、そこから500元を引き出したいのですが、突然のネットワークの変動により、この操作がこの時点で2回繰り返されるため、
ここに画像の説明を挿入
実行後は A != V であるため、最初の控除しか実行できないため、2 番目のスレッドは引き続きスピンして比較します.この
ここに画像の説明を挿入
とき、ルームメイトが数年前に借りた 500 元を返済し、あなたの金額を返済することが起こります. 1000 になる
ここに画像の説明を挿入
この時、スレッド 2 はあなたから 500 を差し引いたので、あなたは 500 元を引き出しましたが、予想外に 1000 を差し引きました
ここに画像の説明を挿入
バージョン番号の仕組みを導入しバージョン番号が同じ場合のみ交換操作を行うことができます
ここに画像の説明を挿入
ルームメイトがあなたに送金した場合、値が変わったためバージョン番号も修正されました.
ここに画像の説明を挿入
このとき、 A と V の値は同じですが、バージョン番号が異なるため、交換はできません


CASのメリットとデメリット

アドバンテージ

  • 同時実行量が少ない場合や、変数を変更する操作が少ない場合は、ユーザー モードとカーネル モードを切り替える必要がないため、従来のロックよりも効率が高くなります。

欠点

  • 比較して置換するためのスピン 同時実行の量が多い場合、変数が常に更新され、スピンが継続的に実行され、過度の CPU 負荷が発生するため、比較が成功しない場合があります。
  • CAS は 1 つの変数のアトミック性のみを保証できますが、コード ブロック全体のアトミック性は保証できません。そのため、複数の変数のアトミックな更新を処理する場合はロックする必要があります。
  • 上記のABAの問題は、バージョン番号を導入することで解決できます

Mutex vs スピンロック

ミューテックス ロックとスピン ロックは、2 つの最下位レベルのロックです. ほとんどの高度なロックは、それらに基づいて実装されます. 以下でそれらの違いについて説明しましょう.

ミューテックス

ミューテックスはスリープ ロックです。つまり、スレッドがロックを占有すると、ロックに失敗した他のスレッドはスリープします。

たとえば、2 つのスレッド A と B が同時にミューテックスをめぐって競合しているとします. スレッド A がミューテックスの取得に成功すると, ロックはスレッド A によって独占されます. ロックを解放する前に, B のロック操作は失敗します. このとき, スレッドB は、それ自体がブロックされている間、CPU を他のスレッドに譲ります。

ミューテックスロック失敗後のブロッキング現象については、下図のようにオペレーティングシステムのカーネルによって実現されます。

ここに画像の説明を挿入

  • ロックに失敗すると、カーネルはスレッドをスリープ状態にし、CPU を他のスレッドに切り替えて実行します。この時点でユーザー モードからカーネル モードに切り替えます
  • ロックが解除されると、カーネルはスレッドをレディ状態にし、適切なタイミングでスレッドを起こしてロックを取得し、業務を継続します。この時点で、カーネル モードからユーザー モードに切り替えます。

そのため、ミューテックス ロックが失敗すると、2 つのコンテキスト スイッチのオーバーヘッドが伴い、ロック時間が短い場合、コンテキスト スイッチ時間がロック時間よりも長くなる可能性があります。

ミューテックスの使用はそれほど難しくありませんが、コンテキスト切り替えのオーバーヘッドを考慮して、場合によってはスピンロックを優先します。


スピンロック

スピンロックはCAS に基づいて実装されており、ユーザーモードでロックおよびロック解除操作を完了し、アクティブにコンテキストを切り替えないため、そのオーバーヘッドはミューテックスロックよりも小さくなります。

ロックを取得しようとするスレッドは、ロックが取得されるまで試行 (つまり、スピン) し続け、同時にスピン ロックを取得できるスレッドは 1 つだけです。

スピン ロックの本質は、実際にはメモリ内の整数に対する CAS 操作です。ロックには次の手順が含まれます。

  1. 整数の値を確認します。0 の場合はロックが解放されていることを意味し、2 番目のステップに進みます。1 の場合はロックがビジーであることを意味し、3 番目のステップに進みます。
  2. 整数の値を 1 に設定すると、現在のスレッドがクリティカル セクションに入る
  3. 整数の値が 0 になるまで、スピン チェックを続けます (ステップ 1 に戻ります)。

上記からわかるように、スピン ロックの取得に失敗したスレッドは常にビジー待機状態であり、ロック リソースが取得されるまで継続的にスピンします。ロック リソースを取得するには、できるだけ早くロックを解放する必要があります。多くの CPU リソース


比較と適用シナリオ

スピンロックとミューテックスの障害戦略が異なるため、スピンロックはビジー待機の戦略を採用し、ミューテックスはスレッド切り替えの戦略を採用しています.異なる戦略により、それらのアプリケーションシナリオも異なります.

スピンロックはスレッド切り替えを必要としないため、完全にユーザーモードで実装され、ロックのオーバーヘッドは低いですが、ビジー待ち戦略を採用しているため、短期的なロックには問題ありませんが、長時間のロックには問題がありません。 -term ロック. CPU リソースの過度の消費。また、スリープしないため、割り込みハンドラで使用できます。

ミューテックスはスレッド切り替えの戦略を採用しており、別のスレッドに切り替えると、元のスレッドはスリープ (ブロック) 状態になるため、スリープの要件がある場合は、ミューテックスの使用を検討できます。また、スリープは CPU リソースを占有しないため、長期ロックではスピンロックよりも大きな利点があります。

特定のアプリケーション シナリオを以下の表に示します。

必要 ロック方法
低オーバーヘッドのロック スピンロック
短期ロック スピンロック
長期ロック ミューテックス
割り込みコンテキストでロックする スピンロック
ロックを保持するにはスリープが必要です ミューテックス

読み書きロック

読み書きロックは、読み取り操作と書き込み操作を明確に区別するために使用されます。

その核心は、独占的な執筆と共有された読書にあります

  • 読み取りロックは 共有ロック. 書き込みロックを保持しているスレッドがない場合、複数のスレッドが同時に読み取りロックを保持できるため、共有リソースのアクセス効率が大幅に向上します。読み取りロックには読み取り許可しかないため、スレッド セーフの問題はありません。
  • 書き込みロックとは排他ロック(exclusive lock)のことで、いずれかのスレッドが書き込みロックを保持すると、他のスレッドが読み取りロックや書き込みロックを取得する操作がブロックされます。

以下に示すように

読み取りロック 書き込みロック
読み取りロック 互換性 非互換
書き込みロック 非互換 非互換

実現方法

さまざまな実装方法に従って、読み取り/書き込みロックは、リーダー優先、ライター優先、および読み取り/書き込みロックに分割されます。

読者第一

リーダーは、読み取りスレッドの同時実行性を向上させるために、より多くのスレッドが読み取りロックを保持できることを最初に期待します。

これを行うためのルールは次のとおりです。スレッドが書き込みロックを申請しても、コンテンツを読み取るリーダーがまだ存在する限り、他の読み取りスレッドは引き続き読み取りロックを申請でき、プロセスは書き込みロックの申請は、読み取りスレッドが読み取りを行っていない場合にのみスレッドが書き込みを許可されるまでブロックされます。

プロセスは下の図のようになります
ここに画像の説明を挿入

ライターファースト

ライター優先度は、書き込みプロセスを優先することです

この時点で、リーダー スレッドが既に読み取りロックを保持して読み取りを行っている一方で、別のライター スレッドが書き込みロックを適用し、ライター スレッドがブロックされているとします。ライターの優先度を確保するために、後続のリーダー スレッドは、読み取りロックを取得するとブロックされます。前の読み取りスレッドが読み取りロックを解放すると、書き込みスレッドが書き込み操作を実行し、書き込みスレッドが書き込みを完了するまで他のスレッドはブロックされます。

プロセスは下の図のようになります
ここに画像の説明を挿入

リテラシーの公平性

上記2つのルールからわかるように、読み書き優先は相手の飢餓につながります

  • リーダーが優先される場合、読み取りプロセスの並行性は高くなりますが、読み取りロックを取得するプロセスが常に存在する場合、書き込みプロセスは書き込みロックを取得できず、この時点で書き込みプロセスは飢えます。
  • 書き込みプロセスが優先される場合、書き込みプロセスが枯渇しないことは保証できますが、書き込みプロセスが書き込みロックを取得している場合、読み取りプロセスは読み取りロックを取得できず、読み取りプロセスはこの時点で飢えます。

一方を優先するともう一方が餓死するので、読み書きのルールは公平に

実装方法:キューを使用して、ロックを取得するスレッドをキューに入れます. 書き込みスレッドと読み取りスレッドの両方が先入れ先出しの原則に従ってロックされます. これはまた, 読み取りスレッドが依然として並行することができることを意味します.そして飢えはありません。


読み書きロック VS ミューテックス ロック

パフォーマンスの点では、読み取り/書き込みロックはミューテックス ロックよりも効率的ではありません。読み取りロックとロックのオーバーヘッドは、現在のリーダーの数をリアルタイムで維持する必要があるため、mutex のオーバーヘッドよりも小さくありません.クリティカル セクションが小さく、ロックの競合が激しくない場合、多くの場合、mutex の効率は高速です.

読み書きロックはミューテックス ロックほど高速ではないかもしれませんが、同時実行性は良好です。同時実行性の要件が高い場所では、読み書きロックを優先する必要があります。

おすすめ

転載: blog.csdn.net/qq_44443986/article/details/117256606