Вы можете абсолютно быть потокобезопасным в использовании неизменяемого словаря. Сама структура данных отлично потокобезопасна, но вы применяете изменения к ней в многопоточной среде, должны быть тщательно написаны, чтобы избежать потери данных в вашем собственном коде.
Вот шаблон, который я часто использую для такого сценария. Он не требует блокировок, поскольку единственная мутация, которую мы делаем, - это одно назначение памяти. Если вам нужно установить несколько полей, вам нужно использовать блокировку.
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));
}
}
Да, я считаю, что это не потокобезопасны в том, что вы описали. Возможно, вам понадобится сделать свой собственный замок вокруг него. –
Каков сценарий, когда у вас есть несколько потоков, пытающихся вставить один и тот же элемент? Если вы собираетесь распараллелить свою работу, вам нужно будет разбить свои данные на потоки/машины. –
Если задание было '_dict [key] = value;' - возможно, даже удаление «ContainsKey» чек - это было бы поточно-безопасным (?) –