14

パターンを実装して進行状況を追跡しようとしてParallel.ForEachいますが、ロックに関して何かが欠けています。次の例では、 の場合は 1000 までカウントされますthreadCount = 1が、threadCount> 1 の場合はカウントされません。これを行う正しい方法は何ですか?

class Program
{
   static void Main()
   {
      var progress = new Progress();
      var ids = Enumerable.Range(1, 10000);
      var threadCount = 2;

      Parallel.ForEach(ids, new ParallelOptions { MaxDegreeOfParallelism = threadCount }, id => { progress.CurrentCount++; });

      Console.WriteLine("Threads: {0}, Count: {1}", threadCount, progress.CurrentCount);
      Console.ReadKey();
   }
}

internal class Progress
{
   private Object _lock = new Object();
   private int _currentCount;
   public int CurrentCount
   {
      get
      {
         lock (_lock)
         {
            return _currentCount;
         }
      }
      set
      {
         lock (_lock)
         {
            _currentCount = value;
         }
      }
   }
}
4

6 に答える 6

26

複数のスレッド (変数count++を共有する) からのようなものを呼び出す際の通常の問題は、次の一連のイベントが発生する可能性があることです。count

  1. スレッド A は の値を読み取りますcount
  2. スレッド B は の値を読み取りますcount
  3. スレッド A はそのローカル コピーをインクリメントします。
  4. スレッド B はそのローカル コピーをインクリメントします。
  5. スレッド A は、インクリメントされた値を に書き戻しますcount
  6. スレッド B は、インクリメントされた値を に書き戻しますcount

このように、スレッド A によって書き込まれた値はスレッド B によって上書きされるため、値は実際には 1 回だけインクリメントされます。

あなたのコードは操作 1、2 ( get) と 5、6 ( ) の周りにロックを追加しますsetが、問題のある一連のイベントを防ぐことはできません。

スレッド A が値をインクリメントしている間、スレッド B がまったくアクセスできないように、操作全体をロックする必要があります。

lock (progressLock)
{
    progress.CurrentCount++;
}

インクリメントのみが必要であることがわかっている場合は、Progressこれをカプセル化するメソッドを作成できます。

于 2013-01-24T12:48:33.033 に答える
25

古い質問ですが、もっと良い答えがあると思います。

この方法を使用して進行状況を報告できInterlocked.Increment(ref progress)ます。進行中の書き込み操作のロックについて心配する必要はありません。

于 2015-05-28T16:58:27.177 に答える
1

最も簡単な解決策は、実際にはプロパティをフィールドに置き換えることでした。

lock { ++progress.CurrentCount; }

(私は個人的に、ポストインクリメントよりもプリインクリメントの外観を好みます。"++." が頭の中で衝突するからです! しかし、ポストインクリメントはもちろん同じように機能します。)

フィールドを更新するメソッドを呼び出すよりもフィールドを更新する方が高速であるため、これにはオーバーヘッドと競合が減少するという追加の利点があります。

もちろん、それをプロパティとしてカプセル化することにも利点があります。IMO、フィールドとプロパティの構文は同一であるため、プロパティが自動実装または同等である場合に、フィールドよりもプロパティを使用する唯一の利点は、依存関係を構築および展開することなく 1 つのアセンブリを展開するシナリオがある場合です。新たにアセンブリ。それ以外の場合は、より高速なフィールドを使用することもできます! 値を確認したり、副作用を追加したりする必要が生じた場合は、フィールドをプロパティに変換して再度ビルドするだけです。したがって、多くの実際のケースでは、フィールドを使用しても問題はありません。

しかし、私たちは多くの開発チームが独断的に運営し、StyleCop のようなツールを使用して独断を強化する時代に生きています。そのようなツールは、コーダーとは異なり、フィールドの使用が許容されるかどうかを判断するほどスマートではないため、常に「StyleCop でさえチェックできるほど単純なルール」は、「フィールドをプロパティとしてカプセル化する」、「パブリック フィールドを使用しない」になります。など...

于 2014-03-25T08:53:50.320 に答える
0

ここでの問題は、++アトミックではないことです。1 つのスレッドが、別のスレッドが値を読み取ってから (現在は正しくない) インクリメントされた値を格納するまでの間、値を読み取ってインクリメントできます。これはおそらく、あなたのをラップするプロパティがあるという事実によって悪化しますint

例えば

Thread 1        Thread 2
reads 5         .
.               reads 5
.               writes 6
writes 6!       .

lockセッターとゲッターの周りのロックは、ブロック自体が順不同で呼び出されるのを止めるものがないため、これには役立ちません。

通常は を使用することをお勧めしInterlocked.Incrementますが、これをプロパティで使用することはできません。

代わりに、呼び出しの周りにブロックを公開_lockして配置することができます。lockprogress.CurrentCount++;

于 2013-01-24T12:48:03.270 に答える
0

プロパティからロック ステートメントを削除し、本体を変更します。

 object sync = new object();
        Parallel.ForEach(ids, new ParallelOptions {MaxDegreeOfParallelism = threadCount},
                         id =>
                             {
                                 lock(sync)
                                 progress.CurrentCount++;
                             });
于 2013-01-24T12:27:13.960 に答える