2

概要:

それは私には思われる:

  1. 論理状態を表すフィールドを単一の不変の消費可能なオブジェクトにラップする
  2. への呼び出しでオブジェクトの正式な参照を更新するInterlocked.CompareExchange<T>
  3. 更新の失敗を適切に処理する

は、「ロック」構造を不必要にするだけでなく、並行性に関する特定の現実をかわし、結果として多くの新しい問題を引き起こす真に誤解を招くような構造にする一種の並行性を提供します。

問題のディスカッション:

まず、ロックを使用する際の主な問題を考えてみましょう。

  1. ロックはパフォーマンス ヒットを引き起こすため、読み取りと書き込みには同時に使用する必要があります。
  2. ロックはスレッドの実行をブロックし、並行性を妨げ、デッドロックのリスクを高めます。

「ロック」に触発されたばかげた動作を考えてみましょう。リソースの論理セットを同時に更新する必要が生じた場合、リソースのセットを「ロック」します。これは、関連付けが緩い専用のロック オブジェクトを介して行います。これは、そうでなければ何の役にも立ちません (赤旗 #1)。

次に、「ロック」パターンを使用して、一連のデータ フィールドで論理的に一貫した状態変更が発生するコードの領域をマークオフしますが、フィールドを同じオブジェクト内の無関係なフィールドと混合することで自分自身を撃ちます。それらをすべて可変のままにし、これらのさまざまなフィールドを読み取るときにロックを使用する必要があるコーナー (赤旗 #2) に追い込むことで、一貫性のない状態でそれらをキャッチしないようにします。

明らかに、その設計には深刻な問題があります。ロックオブジェクトの慎重な管理 (ロック順序、ネストされたロック、スレッド間の調整、何かをするのを待っている別のスレッドによって使用中のリソースのブロック/待機など) が必要なため、やや不安定です。コンテキスト。また、デッドロックを回避するのは「難しい」と言う人もいますが、実際には非常に簡単です。あなたのためにレースを走るように頼む予定の人の靴を盗まないでください!

解決:

「ロック」の使用を完全に停止します。 一貫性のある状態またはスキーマを表す、破損しない/不変のオブジェクトにフィールドを適切にロールバックします。おそらく、表示名と内部識別子を相互に変換するための辞書のペアであるか、値と次のオブジェクトへのリンクを含むキューのヘッド ノードである可能性があります。それが何であれ、それを独自のオブジェクトにラップし、一貫性のために封印します。

書き込みまたは更新の失敗を可能性として認識し、それが発生したときにそれを検出し、無期限にブロックするのではなく、すぐに (または後で) 再試行するか、別のことを行うかを状況に応じて決定します。

ブロッキングは、実行する必要があると思われるタスクをキューに入れるための簡単な方法のように思えますが、すべてのスレッドが専用でセルフサービス型であるため、システム全体を危険にさらすリスクを冒してそのようなことを行う余裕があるわけではありません。「ロック」を使用して物事をシリアル化するのが面倒であるだけでなく、書き込みが失敗してはならないふりをしようとすることの副作用として、スレッドをブロック/フリーズするため、スレッドが応答しなくなり、役に立たなくなり、他のすべての責任が放棄されます。自分の責任を果たすために他人を助けることが必要な場合があるという事実を知らずに、自分が以前にやろうとしていたことを達成するのを頑固に待ちます。

独立した自発的なアクションが同時に発生している場合、競合状態は正常ですが、制御されていないイーサネットの衝突とは異なり、プログラマーとして、「システム」(つまり、決定論的なデジタル ハードウェア) とその入力を完全に制御できます。 0 か 1 か?) と出力、およびシステムの状態を格納するメモリであるため、ライブロックは問題にならないはずです。さらに、多数のプロセッサが同時に動作している可能性があるという事実を解決するメモリ バリアを使用したアトミック操作があります。

要約する:

  1. 現在の状態オブジェクトを取得し、そのデータを消費して、新しい状態を構築します。
  2. 他のアクティブなスレッドがまったく同じことを行っており、あなたを打ち負かす可能性があることを認識してください。ただし、すべてが「現在の」状態を表す信頼できる参照ポイントを観察します。
  3. Interlocked.CompareExchange を使用して、作業の基になった状態オブジェクトがまだ最新の状態であるかどうかを同時に確認し、それを新しい状態に置き換えます。それ以外の場合は失敗し (別のスレッドが最初に終了したため)、適切な修正アクションを実行します。

最も重要な部分は、失敗をどのように処理し、馬に戻るかです。これは、私たちがライブロックを避ける場所であり、考えすぎたり、十分なことをしたり、正しいことをしたりしません。ロックは、スタンピードに乗っていても馬から落ちることは決してないという幻想を作り出し、スレッドがそのようなファンタジーの土地で空想にふけっている間、システムの残りの部分はバラバラになり、クラッシュして燃えることができます.


では、CompareExchange と不変の論理状態オブジェクトを使用したロックフリーの実装では、「ロック」コンストラクトが実行できること (より安定した方法で) を達成できないことはありますか?

これはすべて、ロックを集中的に処理した後、私が自分で実現したことですが、別のスレッドで検索した後、ロックフリーのマルチスレッドプログラミングは何かを簡単にしますか? 、何百ものプロセッサを備えた高度に並列なシステムに直面するとき、高度に競合するロックを使用する余裕がない場合、ロックフリープログラミングが非常に重要になるだろうと誰かが述べています。

4

6 に答える 6

3

あなたのcompare-exchange-suggestionには大きな欠点が1つあります.短いタスクを好むため、公平ではありません. システムに短いタスクが多数ある場合、長いタスクが完了する可能性は非常に低くなります。

于 2009-09-16T00:01:27.867 に答える
2

レースが発生するための条件は 4 つあります。

  1. 最初の条件は、複数のスレッドからアクセスできるメモリ ロケーションがあることです。通常、これらの場所はグローバル/静的変数であるか、グローバル/静的変数から到達可能なヒープ メモリです。
  2. 2 番目の条件は、プログラムが正しく機能するためには、これらの共有メモリ位置に関連付けられているプロパティ (不変条件と呼ばれることが多い) が true (有効) である必要があることです。通常、更新が正しく行われるためには、更新が行われる前にプロパティが true を保持する必要があります。
  3. 3 番目の条件は、実際の更新の一部で不変のプロパティが保持されないことです。(処理の一部で一時的に無効または false になります)。
  4. 競合が発生するために発生しなければならない 4 つ目の最後の条件は、不変式が壊れている間に別のスレッドがメモリにアクセスし、それによって一貫性のない、または正しくない動作が発生することです。

    1. 複数のスレッドからアクセスできる共有メモリの場所がない場合、またはその共有メモリ変数を削除するか、その変数へのアクセスを 1 つのスレッドのみに制限するコードを記述できる場合、競合状態の可能性はありません。 、何も心配する必要はありません。それ以外の場合は、lock ステートメントまたはその他の同期ルーチンが絶対に必要であり、安全に無視することはできません。

    2. 不変条件がない場合 (たとえば、この共有メモリの場所に書き込むだけで、スレッド操作でその値を読み取るものは何もないとしましょう)、問題はありません。

    3. 不変式が無効にならない場合は、やはり問題ありません。(共有メモリは、コードが最後に実行された日時を格納する日時フィールドであるとします。スレッドがまったく書き込みに失敗しない限り、無効になることはありません...

    4. nbr 4 を排除するには、ロックまたは同等の同期方法を使用して、一度に複数のスレッドから共有メモリにアクセスするコード ブロックへの書き込みアクセスを制限する必要があります。

この場合、「同時ヒット」は避けられないだけでなく、絶対に必要です。共有メモリとは何か、重要な「不変条件」とは何かをインテリジェントに分析することで、システムをコーディングして、この同時実行の「ヒット」を最小限に抑えることができます。(つまり、同時実行性を安全に最大化します。)

于 2009-09-16T01:08:44.453 に答える
1

Interlocked.CompareExchangeのようなCAS操作に対するロックの大きな利点は、ロック内の複数のメモリ位置を変更でき、すべての変更が同時に他のスレッド/プロセスに表示されることです。

CASでは、1つの変数のみがアトミックに更新されます。ロックフリーコードは通常、非常に複雑です。これは、一度に1つの変数(またはCAS2で隣接する2つの変数)の更新のみを他のスレッドに提示できるだけでなく、CASが「失敗」状態を処理できる必要があるためです。成功しません。さらに、ABA問題やその他の起こりうる合併症を処理する必要があります。

ローロック、ファイングレインロック、ストライプロック、リーダーライターロックなどのさまざまな方法があり、単純なロックコードをはるかにマルチコアフレンドリーにすることができます。

とはいえ、ロックコードとロックフリーコードの両方には興味深い用途がたくさんあります。ただし、自分が何をしているのかを本当に理解していない限り、独自のロックフリーコードを作成するのは初心者向けではありません。多くのロックフリー試行で失敗の原因となるエッジ条件を見つけるのは非常に難しいため、ロックフリーコードまたは十分に証明されたアルゴリズムを使用して、それらを徹底的にテストします。

于 2009-10-30T16:18:10.883 に答える
1

ロックフリーのプログラミングスタイルを使用して、このタスクをどのように実行するか知りたいですか? 次のジョブを実行するために、タスクの共有リストを定期的にヒットする多くのワーカー スレッドがあります。(現在) リストをロックし、先頭の項目を見つけて削除し、リストのロックを解除します。2 つのスレッドが同じタスクで動作してしまうことがないように、またはタスクが誤ってスキップされないように、すべてのエラー条件とデータ競合の可能性を考慮してください。

これを行うためのコードは複雑すぎるという問題があり、競合が激しい場合にはパフォーマンスが低下する可能性があるのではないかと思います。

于 2009-09-16T00:01:08.787 に答える
1

一般に、楽観的同時実行性を考えると悲観的同時実行性は時代遅れである、またはパターン B のためにパターン A は時代遅れであると言うのと同じくらい時代遅れではないと言えます。それはコンテキストに関するものだと思います。ロックフリーは強力ですが、すべての問題がこれに完全に適しているわけではないため、一方的に適用しても意味がない場合があります。トレードオフがあります。とはいえ、従来は実現されていなかった、汎用のロックレスで楽観的なアプローチがあればよいでしょう。要するに、はい、ロックは、他のアプローチでは達成 できないことを行うことができます。つまり、潜在的に単純なソリューションを表します。繰り返しますが、特定のことが問題にならない場合、両方が同じ結果になる可能性があります。私はちょっと言い訳をしていると思います...

于 2010-07-27T03:12:53.140 に答える
0

理論的には、実行する作業量が決まっている場合、 を使用するプログラムは、Interlocked.CompareExchangeロックせずにすべての作業を行うことができます。残念なことに、競合が激しい場合、read/compute-new/compareExchange ループがひどくスラッシングする可能性があり、100 個のプロセッサがそれぞれ共通のデータ項目に対して 1 つの更新を実行しようとすると、リアルタイムでより長い時間がかかる可能性があります。 1 つのプロセッサが 100 の更新を順番に実行するよりも。並列処理によってパフォーマンスが向上するわけではありません。リソースを保護するためにロックを使用すると、一度に 1 つの CPU だけがリソースを更新できますが、シングル CPU の場合と同じようにパフォーマンスが向上します。

ロックフリー プログラミングの本当の利点の 1 つは、スレッドが任意の時間ウェイレイされても、システム機能に悪影響が及ばないことです。CompareExchangeロックとタイムアウトの組み合わせを使用することで、純粋ベースのプログラミングのパフォーマンスの落とし穴を回避しながら、その利点を維持できます。基本的な考え方は、競合が存在する場合、リソースはロックベースの同期に切り替わりますが、スレッドがロックを長時間保持している場合、新しいロック オブジェクトが作成され、以前のロックは無視されるというものです。これは、前のスレッドがまだサイクルを実行しようとしている場合、CompareExchange失敗する (そして最初からやり直す必要がある) ことを意味しますが、後のスレッドはブロックされず、正確性も犠牲になりません。

上記のすべてを調停するために必要なコードは複雑で扱いにくいことに注意してください。ただし、特定の障害状態が存在する場合にシステムを堅牢にしたい場合は、そのようなコードが必要になる場合があります。

于 2013-02-12T00:27:14.430 に答える