6

複数のスレッドから呼び出されるクラスがあり、このクラスのプライベート フィールドのImmutableDictionaryにデータを格納するとします。

public class Something {
    private ImmutableDictionary<string,string> _dict;
    public Something() {
       _dict = ImmutableDictionary<string,string>.Empty;
    }

    public void Add(string key, string value) {

       if(!_dict.ContainsKey(key)) {
          _dict = _dict.Add(key,value);
       }
    }
}

これを複数のスレッドで呼び出して、辞書に既に存在するキーに関するエラーが発生する可能性はありますか?

スレッド 1 は、辞書が false であることを確認します。 スレッド 2 は、辞書が false であることを確認します。 スレッド 1 が値を追加し、_dict への参照が更新されます。

4

4 に答える 4

5

不変の辞書を使用することで、絶対にスレッドセーフにすることができます。データ構造自体は完全にスレッドセーフですが、マルチスレッド環境で変更を適用する場合は、独自のコードでデータが失われないように慎重に記述する必要があります。

これは、まさにそのようなシナリオで私が頻繁に使用するパターンです。私たちが行う唯一の変更は単一のメモリ割り当てであるため、ロックは必要ありません。複数のフィールドを設定する必要がある場合は、ロックを使用する必要があります。

using System.Threading;

public class Something {
    private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;

    public void Add(string key, string value) {
       // It is important that the contents of this loop have no side-effects
       // since they can be repeated when a race condition is detected.
       do {
          var original = _dict;
          if (local.ContainsKey(key)) {
             return;
          }

          var changed = original.Add(key,value);
          // The while loop condition will try assigning the changed dictionary
          // back to the field. If it hasn't changed by another thread in the
          // meantime, we assign the field and break out of the loop. But if another
          // thread won the race (by changing the field while we were in an 
          // iteration of this loop), we'll loop and try again.
       } while (Interlocked.CompareExchange(ref this.dict, changed, original) != original);
    }
}

実際、私はこのパターンを頻繁に使用するため、この目的のために静的メソッドを定義しました。

/// <summary>
/// Optimistically performs some value transformation based on some field and tries to apply it back to the field,
/// retrying as many times as necessary until no other thread is manipulating the same field.
/// </summary>
/// <typeparam name="T">The type of data.</typeparam>
/// <param name="hotLocation">The field that may be manipulated by multiple threads.</param>
/// <param name="applyChange">A function that receives the unchanged value and returns the changed value.</param>
public static bool ApplyChangeOptimistically<T>(ref T hotLocation, Func<T, T> applyChange) where T : class
{
    Requires.NotNull(applyChange, "applyChange");

    bool successful;
    do
    {
        Thread.MemoryBarrier();
        T oldValue = hotLocation;
        T newValue = applyChange(oldValue);
        if (Object.ReferenceEquals(oldValue, newValue))
        {
            // No change was actually required.
            return false;
        }

        T actualOldValue = Interlocked.CompareExchange<T>(ref hotLocation, newValue, oldValue);
        successful = Object.ReferenceEquals(oldValue, actualOldValue);
    }
    while (!successful);

    Thread.MemoryBarrier();
    return true;
}

その後、 Add メソッドははるかに簡単になります。

public class Something {
    private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;

    public void Add(string key, string value) {
       ApplyChangeOptimistically(
          ref this.dict,
          d => d.ContainsKey(key) ? d : d.Add(key, value));
    }
}
于 2013-02-07T15:58:07.990 に答える
4

はい、通常と同じ競合が適用されます (両方のスレッドが読み取り、何も検出されず、両方のスレッドが書き込みます)。スレッドセーフは、データ構造のプロパティではなく、システム全体のプロパティです。

別の問題があります: 異なるキーへの同時書き込みは書き込みを失うだけです。

必要なのはConcurrentDictionary. 追加のロックまたは CAS ループがなければ、不変のものでこれを機能させることはできません。

更新:ImmutableDictionaryコメントは、書き込みの頻度が低い場合、書き込みに CAS ループを使用することは実際には非常に良い考えであると確信しました。読み取りパフォーマンスは非常に優れており、同期されたデータ構造で得られるのと同じくらい安価に書き込みます.

于 2013-02-03T10:11:20.667 に答える
1

インスタンス変数にアクセスすると、Add() メソッドは再入不可になります。インスタンス変数へのコピー/再割り当ては、非再入可能性を変更しません (依然として競合状態になりがちです)。この場合の ConcurrentDictionary は、完全な一貫性なしでアクセスを許可しますが、ロックもありません。スレッド間で 100% の一貫性が必要な場合 (可能性は低い)、Dictionary に対する何らかのロックが必要です。可視性スコープは 2 つの異なるものであることを理解することは非常に重要です。インスタンス変数がプライベートかどうかは、そのスコープとは関係がないため、スレッド セーフとは関係ありません。

于 2013-02-05T17:10:54.530 に答える