10

このウェブサイトは初めてですので、受け入れられた方法で投稿していない場合はお知らせください。

私は頻繁に以下のサンプルの線に沿って何かをコーディングしました(明確にするためにDisposeのようなものは省略されています)。私の質問は、示されているように揮発性物質が必要ですか?または、Thread.Startを読んだように、ManualResetEvent.Setには暗黙のメモリバリアがありますか?または、明示的なMemoryBarrier呼び出しは、揮発性物質よりも優れていますか?それとも完全に間違っていますか?また、私が見た限りでは、一部の操作での「潜在記憶バリアの動作」が文書化されていないという事実は非常に苛立たしいものですが、これらの操作のリストはどこかにありますか?

ありがとう、トム

class OneUseBackgroundOp
{

   // background args
   private string _x;
   private object _y;
   private long _z;

   // background results
   private volatile DateTime _a
   private volatile double _b;
   private volatile object _c;

   // thread control
   private Thread _task;
   private ManualResetEvent _completedSignal;
   private volatile bool _completed;

   public bool DoSomething(string x, object y, long z, int initialWaitMs)
   {
      bool doneWithinWait;

      _x = x;
      _y = y;
      _z = z;

      _completedSignal = new ManualResetEvent(false);

      _task = new Thread(new ThreadStart(Task));
      _task.IsBackground = true;
      _task.Start()

      doneWithinWait = _completedSignal.WaitOne(initialWaitMs);

      return doneWithinWait;

   }

   public bool Completed
   {
      get
      {
         return _completed;
      }
   }

   /* public getters for the result fields go here, with an exception
      thrown if _completed is not true; */

   private void Task()
   {
      // args x, y, and z are written once, before the Thread.Start
      //    implicit memory barrier so they may be accessed freely.

      // possibly long-running work goes here

      // with the work completed, assign the result fields _a, _b, _c here

      _completed = true;
      _completedSignal.Set();

   }

}
4

5 に答える 5

3

待機関数には、暗黙的なメモリ バリアがあります。http://msdn.microsoft.com/en-us/library/ms686355(v=vs.85).aspxを参照してください。

于 2012-05-18T11:15:31.740 に答える
3

volatile キーワードを、_a、_b、および _c をスレッドセーフにすることと混同しないでください。より良い説明については、こちらを参照してください。さらに、ManualResetEvent は、_a、_b、および _c のスレッド セーフとは関係ありません。個別に管理する必要があります。

編集: この編集では、この質問に関するさまざまな回答とコメントに含まれているすべての情報を抽出しようとしています。

基本的な問題は、フラグ変数 (_completed) が true を返したときに、結果変数 (_a、_b、および _c) が「見える」かどうかです。

ここで、どの変数も volatile とマークされていないと仮定しましょう。この場合、次のように、フラグ変数が Task() で設定された後に結果変数が設定される可能性があります。

   private void Task()
   {
      // possibly long-running work goes here
      _completed = true;
      _a = result1;
      _b = result2;
      _c = result3;
      _completedSignal.Set();
   }

これは明らかに私たちが望んでいることではありません。

これらの変数が volatile とマークされている場合、この並べ替えは防止されます。しかし、それが元の質問を促したものです-揮発性が必要ですか、それともManualResetEventは並べ替えが発生しないように暗黙的なメモリバリアを提供しますか?その場合、揮発性キーワードは実際には必要ありませんか?

私の理解が正しければ、wekempf の立場は、WaitOne() 関数が問題を解決する暗黙のメモリ バリアを提供するというものです。 しかし、それは私には十分ではないようです。メイン スレッドとバックグラウンド スレッドは、2 つの別個のプロセッサで実行できます。そのため、Set() が暗黙的なメモリ バリアも提供しない場合、Task() 関数はプロセッサの 1 つで次のように実行される可能性があります (揮発性変数を使用しても)。

   private void Task()
   {
      // possibly long-running work goes here
      _completedSignal.Set();
      _a = result1;
      _b = result2;
      _c = result3;
      _completed = true;
   }

メモリ バリアと EventWaitHandles に関する情報を高低で検索しましたが、何も思いつきませんでした。私が見た唯一の参考文献は、wekempf が Jeffrey Richter の本に対して作成したものです。これに関する問題は、EventWaitHandle がデータへのアクセスではなく、スレッドの同期を意図していることです。EventWaitHandle (ManualResetEvent など) を使用してデータへのアクセスを同期する例を見たことがありません。そのため、EventWaitHandle がメモリ バリアに関して何かを行うとは信じがたいです。それ以外の場合は、インターネット上でこれに関する参照が見つかると思います。

編集 #2: これは、私の応答に対する wekempf の応答に対する応答です... ;)

amazon.com にある Jeffrey Richter の本のセクションを読むことができました。628ページから(wekempfもこれを引用しています):

最後に、スレッドがインターロックされたメソッドを呼び出すたびに、CPU がキャッシュの一貫性を強制することを指摘しておく必要があります。したがって、インターロックされたメソッドを介して変数を操作している場合、このメモリ モデルのすべてについて心配する必要はありません。さらに、すべてのスレッド同期ロック ( MonitorReaderWriterLockMutexSemaphoneAutoResetEventManualResetEventなど) は、インターロックされたメソッドを内部的に呼び出します。

そのため、wekempf が指摘したように、ManualResetEvent がキャッシュの一貫性を保証するため、結果変数は例に示すように volatile キーワードを必要としないように思われます。

この編集を閉じる前に、2 つの点を追加したいと思います。

まず、私の最初の想定は、バックグラウンド スレッドが複数回実行される可能性があるというものでした。クラスの名前 (OneUseBackgroundOp) を明らかに見落としていました。一度しか実行されないことを考えると、DoSomething() 関数がそのような方法で WaitOne() を呼び出す理由は明らかではありません。DoSomething() が戻るときにバックグラウンド スレッドが完了している場合と完了していない場合に、initialWaitMs ミリ秒待機する意味は何ですか? バックグラウンド スレッドを開始し、ロックを使用して結果変数へのアクセスを同期するか、単に DoSomething() を呼び出すスレッドの一部として Task() 関数の内容を実行しないのはなぜですか? これをしない理由はありますか?

第 2 に、結果の変数に対してある種のロック メカニズムを使用しないことは、依然として悪いアプローチであるように思われます。確かに、示されているコードでは必要ありません。しかし、ある時点で、別のスレッドが現れてデータにアクセスしようとする可能性があります。不可解な動作異常を後で追跡しようとするよりも、今この可能性に備える方が良いと思います.

これについて私と一緒にいてくれてありがとう。このディスカッションに参加することで、私は確かに多くのことを学びました。

于 2009-03-25T14:37:37.970 に答える
3

これは、コードを詳しく調べずに、カフから外れていることに注意してください。Set がメモリバリアを実行するとは思いませんが、それがコードにどのように関連しているのかわかりませんか? Wait が 1 つを実行するかどうかは、より重要なようです。したがって、コードを確認するために費やした 10 秒間で何かを見落としていない限り、揮発性は必要ないと思います。

編集: コメントが制限的すぎます。私は今、マットの編集を参照しています。

マットの評価はうまくいきましたが、詳細が欠けています。最初に、あちこちに散らばっているが、ここでは明確にされていないもののいくつかの定義を提供しましょう。

揮発性読み取りは、値を読み取り、CPU キャッシュを無効にします。揮発性書き込みは、キャッシュをフラッシュしてから、値を書き込みます。メモリバリアはキャッシュをフラッシュしてから無効にします。

.NET メモリ モデルは、すべての書き込みが揮発性であることを保証します。デフォルトでは、明示的な VolatileRead が作成されるか、フィールドに volatile キーワードが指定されない限り、読み取りは行われません。さらに、インターロックされたメソッドはキャッシュの一貫性を強制し、すべての同期の概念 (Monitor、ReaderWriterLock、Mutex、Semaphore、AutoResetEvent、ManualResetEvent など) はインターロックされたメソッドを内部的に呼び出し、キャッシュの一貫性を保証します。

繰り返しますが、これはすべて Jeffrey Richter の著書「CLR via C#」からのものです。

私は当初、セトが記憶バリアを実行したとは思わないと言いました。しかし、Richter 氏の発言をさらに考えると、Set は連動して動作し、キャッシュの一貫性も確保されます。

ここでは volatile は必要ないという最初の主張を支持します。

編集 2:まるで「未来」を構築しているように見えます。自分でロールバックするのではなく、PFXを調べることをお勧めします。

于 2009-03-25T18:09:21.643 に答える
1

まず、「自分の質問に答える」べきか、コメントを使用するべきかわかりませんが、次のようになります。

私の理解では、揮発性は、結果を読み取るスレッドが最新のデータを参照できるように、コード/メモリの最適化が結果変数 (および完成したブール値) へのアクセスを移動するのを防ぎます。

_completed ブール値がSet() のにすべてのスレッドに表示されるのは望ましくありません。これは、コンパイラまたは空の最適化/並べ替えのためです。同様に、Set() の後に結果 _a、_b、_c への書き込みが見られるのは望ましくありません。

編集:マット・デイビスが言及した項目に関して、質問に関するさらなる説明/明確化:

最後に、スレッドがインターロックされたメソッドを呼び出すたびに、CPU がキャッシュの一貫性を強制することを指摘しておく必要があります。したがって、インターロックされたメソッドを介して変数を操作している場合、このメモリ モデルのすべてについて心配する必要はありません。さらに、すべてのスレッド同期ロック (Monitor、ReaderWriterLock、Mutex、Semaphone、AutoResetEvent、ManualResetEvent など) は、インターロックされたメソッドを内部的に呼び出します。

そのため、wekempf が指摘したように、ManualResetEvent がキャッシュの一貫性を保証するため、結果変数は例に示すように volatile キーワードを必要としないように思われます。

したがって、そのような操作がプロセッサ間またはレジスタ内などのキャッシュを処理することに同意します。

しかし、結果が完了フラグの前に割り当てられ、ManualResetEvent が設定される前に完了フラグが true に割り当てられることの両方を保証するために並べ替えを防止ます ?

まず、私の最初の想定は、バックグラウンド スレッドが複数回実行される可能性があるというものでした。クラスの名前 (OneUseBackgroundOp) を明らかに見落としていました。1 回しか実行されないことを考えると、DoSomething() 関数がそのような方法で WaitOne() を呼び出す理由は明らかではありません。DoSomething() が戻るときにバックグラウンド スレッドが完了している場合と完了していない場合に、initialWaitMs ミリ秒待機する意味は何ですか? バックグラウンド スレッドを開始し、ロックを使用して結果変数へのアクセスを同期するか、単に DoSomething() を呼び出すスレッドの一部として Task() 関数の内容を実行しないのはなぜですか? これをしない理由はありますか?

サンプルのコンセプトは、実行時間の長いタスクを実行することです。例外的な時間内にタスクを完了できる場合、呼び出し元のスレッドは結果にアクセスし、通常の処理を続行します。ただし、タスクに非常に長い時間がかかる場合があり、その期間は要求スレッドをブロックできず、それに対処するための適切な手順を実行できます。これには、Completed プロパティを使用して操作を後で確認することも含まれます。

具体的な例: DNS 解決は多くの場合非常に高速 (1 秒未満) で、GUI からでも待つ価値がありますが、場合によっては何秒もかかることがあります。したがって、サンプルのようなユーティリティ クラスを使用すると、95% の確率で呼び出し元の視点から簡単に結果を取得でき、残りの 5% は GUI をロックアップしません。バックグラウンド ワーカーを使用することもできますが、ほとんどの場合、それほど配管を必要としない操作にはやり過ぎになる可能性があります。

第 2 に、結果変数に対してある種のロック メカニズムを使用しないことは、依然として悪いアプローチであるように思えます。確かに、示されているコードでは必要ありません。

結果 (および完了フラグ) のデータは、1 回だけ書き込み、何度も読み取ることが意図されています。結果とフラグを割り当てるためにロックを追加した場合、結果のゲッターもロックする必要があり、データ ポイントを返すためだけにゲッターがロックするのを見るのは好きではありませんでした。私の読書によると、このようなきめの細かいロックは適切ではありません。操作の結果が 5 つまたは 6 つある場合、呼び出し元は不必要にロックの取得と解放を 5 回または 6 回行う必要があります。

しかし、ある時点で、別のスレッドが現れてデータにアクセスしようとする可能性があります。不可解な動作異常を後で追跡しようとするよりも、今この可能性に備える方が良いと思います.

揮発性の結果が設定される前に設定されることが保証されている揮発性の完了フラグがあり、結果への唯一のアクセスはゲッターを介して行われるため、smaple で述べたように、ゲッターが呼び出されて操作が実行されると例外がスローされます。はまだ完了していません。Completed および結果のゲッターは、DoSomething() を呼び出したスレッド以外のスレッドから呼び出すことができると思います。とにかくそれが私の希望です。とにかく、これは揮発性物質に当てはまると思います。

于 2009-03-25T19:49:34.040 に答える
0

あなたが示したことに基づいて、いいえ、volatilesそのコードでは必要ありません。

自体には、暗黙的なメモリ バリアはManualResetEventありません。ただし、メイン スレッドがシグナルを待機しているという事実は、変数を変更できないことを意味します。少なくとも、待機中は変数を変更できません。したがって、同期オブジェクトを待機することは、暗黙的なメモリ バリアであると言えます。

ただし、他のスレッドが存在し、それらの変数にアクセスできる場合は、それらの変数を変更できることに注意してください。

あなたの質問から、あなたは何をするかという点を見逃しているようですvolatile。変数volatileが他のスレッドによって非同期的に変更される可能性があることをコンパイラに伝えるだけなので、変数にアクセスするコードを最適化するべきではありません。 volatile変数へのアクセスを同期しません。

于 2009-03-25T14:32:24.457 に答える