2013-02-01 2 views
5

Скажем, у меня есть класс, который будет вызываться из нескольких потоков, и буду хранить некоторые данные в качестве ImmutableDictionary в частной области в этом классеИспользование Bcl 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 проверяет словарь видит ложный Резьба2 проверяют словарь видит ложный Резьба1 добавляют значение и ссылка на _dict обновляются Резьба2 добавляют значение, но это уже добавлено, поскольку он использует ту же ссылку?

+1

Да, я считаю, что это не потокобезопасны в том, что вы описали. Возможно, вам понадобится сделать свой собственный замок вокруг него. –

+0

Каков сценарий, когда у вас есть несколько потоков, пытающихся вставить один и тот же элемент? Если вы собираетесь распараллелить свою работу, вам нужно будет разбить свои данные на потоки/машины. –

+0

Если задание было '_dict [key] = value;' - возможно, даже удаление «ContainsKey» чек - это было бы поточно-безопасным (?) –

ответ

3

Да, действует та же гонка, что и обычная (оба потока читаются, ничего не находят, то оба потока пишут). Безопасность нитей не является свойством структуры данных, а всей системы.

Есть еще одна проблема: одновременная запись на разные клавиши будет только потерять записи.

Что вам нужно - ConcurrentDictionary. Вы не можете сделать эту работу с неизменяемой без дополнительной блокировки или CAS-петли.

Обновление: Комментарии убедили меня, что ImmutableDictionary, используемый с CAS-контуром для записи, на самом деле очень хорошая идея, если записи нечасты. Чтение производительности будет очень приятным и будет дешевым, как и с синхронизированной структурой данных.

+0

Разница в том, что с 'Dictionary' вы не можете использовать CAS, вы должны использовать замок, который * может * изменить ситуацию. – svick

+0

@svick, строго говоря, вы * можете * использовать CAS с Dictionary ... Но это ничтожное, и вы правы. – usr

+0

Мне удалось обойти дублирующий ключ, скопировав словарь в локальную переменную, выполнив эту функцию, а затем вернув результат в частное поле, но вы хорошо понимаете, что одновременная запись на разные ключи будет потеряна. Я полагаю, что в этом случае какой-то ImmutableSet, вероятно, лучший выбор, а затем сделать копию локально и объединить результат. – chrisortman

1

Доступ к переменной экземпляра делает метод Add() невозвратным. Копирование/повторное присвоение переменной экземпляра не изменяет не-повторное размещение (оно все еще зависит от условий гонки). ConcurrentDictionary в этом случае позволит получить доступ без полной согласованности, но также и без блокировки. Если существует потребность в 100% -ной согласованности по нитям (маловероятно), тогда необходим какой-то замок в словаре. Очень важно понимать, что видимость и scope - это две разные вещи. Независимо от того, является ли переменная экземпляра частной или нет, она не влияет на ее объем и, следовательно, на безопасность потока.

3

Вы можете абсолютно быть потокобезопасным в использовании неизменяемого словаря. Сама структура данных отлично потокобезопасна, но вы применяете изменения к ней в многопоточной среде, должны быть тщательно написаны, чтобы избежать потери данных в вашем собственном коде.

Вот шаблон, который я часто использую для такого сценария. Он не требует блокировок, поскольку единственная мутация, которую мы делаем, - это одно назначение памяти. Если вам нужно установить несколько полей, вам нужно использовать блокировку.

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)); 
    } 
} 
Смежные вопросы