不変の辞書を使用することで、絶対にスレッドセーフにすることができます。データ構造自体は完全にスレッドセーフですが、マルチスレッド環境で変更を適用する場合は、独自のコードでデータが失われないように慎重に記述する必要があります。
これは、まさにそのようなシナリオで私が頻繁に使用するパターンです。私たちが行う唯一の変更は単一のメモリ割り当てであるため、ロックは必要ありません。複数のフィールドを設定する必要がある場合は、ロックを使用する必要があります。
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));
}
}