40

確認中…_count安全にアクセスされていますよね?

どちらのメソッドも、複数のスレッドによってアクセスされます。

private int _count;

public void CheckForWork() {
    if (_count >= MAXIMUM) return;
    Interlocked.Increment(ref _count);
    Task t = Task.Run(() => Work());
    t.ContinueWith(CompletedWorkHandler);
}

public void CompletedWorkHandler(Task completedTask) {
    Interlocked.Decrement(ref _count);
    // Handle errors, etc...
}
4

6 に答える 6

98

これはスレッドセーフですよね?

MAXIMUM が 1、count が 0、5 つのスレッドが CheckForWork を呼び出すとします。

5 つのスレッドすべてで、count が MAXIMUM 未満であることを確認できました。その後、カウンターは 5 つまで増加し、5 つのジョブが開始されます。

それはコードの意図に反するようです。

さらに、フィールドは揮発性ではありません。では、メモリ バリアがないパスでスレッドが最新の値を読み取ることを保証するメカニズムは何ですか? それを保証するものは何もありません!条件が false の場合にのみ、メモリ バリアを作成します。

より一般的に言えば、あなたはここで偽りの経済を作っています。ローロック ソリューションを使用することで、競合のないロックにかかる数十ナノ秒を節約できます。 ロックを取るだけです。数十ナノ秒余分に余裕があります。

さらに一般的には、プロセッサ アーキテクチャの専門家であり、CPU がローロック パスで実行できるすべての最適化を知っている場合を除き、ローロック コードを記述しないでください。あなたはそのような専門家ではありません。私もそうではない。だからローロックコードは書かない

于 2013-10-25T15:00:07.090 に答える
40

いいえ、if (_count >= MAXIMUM) return;スレッドセーフではありません。

編集:読み取りもロックする必要があります。これは論理的にインクリメントでグループ化する必要があるため、次のように書き直します

private int _count;

private readonly Object _locker_ = new Object();

public void CheckForWork() {
    lock(_locker_)
    {
        if (_count >= MAXIMUM)
            return;
        _count++;
    }
    Task.Run(() => Work());
}

public void CompletedWorkHandler() {
    lock(_locker_)
    {
        _count--;
    }
    ...
}
于 2013-10-25T14:59:37.403 に答える
36

それがSemaphoreSemaphoreSlimの目的です。

private readonly SemaphoreSlim WorkSem = new SemaphoreSlim(Maximum);

public void CheckForWork() {
    if (!WorkSem.Wait(0)) return;
    Task.Run(() => Work());
}

public void CompletedWorkHandler() {
    WorkSem.Release();
    ...
}
于 2013-10-25T16:07:51.617 に答える
22

いいえ、あなたが持っているものは安全ではありません。別のスレッドから_count >= MAXIMUMの呼び出しと競合する可能性があるかどうかを確認するためのチェック。Interlocked.Incrementこれは、ローロック技術を使用して解決するのは実際には非常に困難ですこれを適切に機能させるには、ロックを使用せずに一連のいくつかの操作をアトミックに見せる必要があります。それが難しい部分です。ここで問題となる一連の操作は次のとおりです。

  • 読んだ_count
  • テスト_count >= MAXIMUM
  • 以上を踏まえて判断してください。
  • 決定_countに応じて増減します。

これらの 4 つのステップすべてをアトミックにしないと、競合状態が発生します。ロックを取得せずに複雑な操作を実行するための標準的なパターンは次のとおりです。

public static T InterlockedOperation<T>(ref T location)
{
  T initial, computed;
  do
  {
    initial = location;
    computed = op(initial); // where op() represents the operation
  } 
  while (Interlocked.CompareExchange(ref location, computed, initial) != initial);
  return computed;
}

何が起こっているかに注意してください。初期値が最初に読み取られてから変更が試行された時点までの間に初期値が変更されていないと ICX 操作が判断するまで、この操作は繰り返し実行されます。これは標準的なパターンであり、CompareExchange(ICX) 呼び出しによって魔法がすべて発生します。ただし、これはABA 問題を考慮していないことに注意してください。1

あなたができること:

したがって、上記のパターンをコードに組み込むと、このような結果になります。

public void CheckForWork() 
{
    int initial, computed;
    do
    {
      initial = _count;
      computed = initial < MAXIMUM ? initial + 1 : initial;
    }
    while (Interlocked.CompareExchange(ref _count, computed, initial) != initial);
    if (replacement > initial)
    {
      Task.Run(() => Work());
    }
}

個人的には、ローロック戦略を完全にパントします。私が上で提示したものにはいくつかの問題があります。

  • これは実際には、ハード ロックを取得するよりも遅くなる可能性があります。理由は説明が難しく、私の回答の範囲外です。
  • 上記の内容から逸脱すると、コードが失敗する可能性があります。はい、それは本当に脆いです。
  • わかりにくいです。つまり、それを見てください。それは醜いです。

あなたがすべきこと:

ハード ロック ルートを使用すると、コードは次のようになります。

private object _lock = new object();
private int _count;

public void CheckForWork() 
{
  lock (_lock)
  {
    if (_count >= MAXIMUM) return;
    _count++;
  }
  Task.Run(() => Work());
}

public void CompletedWorkHandler() 
{
  lock (_lock)
  {
    _count--;
  }
}

これははるかに単純で、エラーが発生しにくいことに注意してください。実際、このアプローチ (ハード ロック) は、上で示した方法 (ロー ロック) よりも高速であることに気付くかもしれません。繰り返しますが、理由はトリッキーで、速度を上げるために使用できる手法がありますが、この回答の範囲外です。


1この場合、ABA の問題は実際には問題ではありません。なぜなら、ロジックは変更され_countないままであることに依存していないからです。その間に何が起こったかに関係なく、その値が 2 つの時点で同じであることが重要です。言い換えれば、実際には値が変化しているにもかかわらず、値が変化していないように見える問題に還元することができます。

于 2013-10-25T15:15:46.657 に答える
4

スレッドセーフを定義します。

_count が MAXIMUM を超えないようにしたい場合は、成功しませんでした。

あなたがすべきことは、それもロックすることです:

private int _count;
private object locker = new object();

public void CheckForWork() 
{
    lock(locker)
    {
        if (_count >= MAXIMUM) return;
        _count++;
    }
    Task.Run(() => Work());
}

public void CompletedWorkHandler() 
{
    lock(locker)
    {
        _count--;
    }
    ...
}

SemaphoreSlimクラスも参照してください。

于 2013-10-25T15:00:22.843 に答える