Уточнение чтения и записи в словаре С#

В контексте этого утверждения

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

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

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

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

Использование ConcurrentDictionary из .Net 4.0 не является вариантом.

Ответ 1

Важнейшим моментом, который еще не было упомянуто, является то, что если TValue - тип класса, то вещи, принадлежащие Dictionary<TKey,TValue>, будут тождествами объектов TValue. Если человек получает ссылку из словаря, словарь не будет знать и не заботится обо всем, что можно сделать с объектом, на который он ссылается.

Один полезный небольшой класс утилиты в случаях, когда все ключи, связанные со словарем, будут известны заранее, код, который необходимо использовать, это:

class MutableValueHolder<T>
{
   public T Value;
}

Если требуется, чтобы многопоточный код подсчитывал, сколько раз различные строки появляются в кучке файлов, и заранее известно все интересующие строки, тогда для этой цели можно использовать что-то вроде Dictionary<string, MutableValueHolder<int>>. После того, как словарь загрузится со всеми правильными строками и экземпляром MutableValueHolder<int> для каждого из них, любое количество потоков может извлекать ссылки на объекты MutableValueHolder<int> и использовать Threading.Interlocked.Increment или другие подобные методы для изменения Value связанных с каждым, без необходимости вообще писать в словарь.

Ответ 2

перезапись существующего значения следует рассматривать как операцию записи

Ответ 3

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

Изменение ключа - наиболее определенно запись, так как он заставит элемент перемещаться во внутреннем хеше или индексе, или, однако, словари делают свой файл O (log (n))...

Что вы можете сделать, это посмотреть на ReaderWriterLock

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

Ответ 4

Обновление значения концептуально является операцией записи. При обновлении значения с одновременным доступом, когда чтение выполняется до завершения записи, вы считываете старое значение. Когда две записи конфликтуют, может быть сохранено неправильное значение.

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

ICollection предоставляет члену для синхронизации доступа, и ссылка остается действительной при выполнении операций увеличения/сжатия.

Ответ 5

Операция чтения - это все, что получает ключ или значение от Dictionary, операция записи - это что-либо, что обновляет или добавляет ключ или значение. Таким образом, процесс обновления ключа будет считаться писателем.

Простым способом создания словабельного словаря является создание собственной реализации IDictionary, которая просто блокирует мьютекс, а затем перенаправляет вызов реализации:

public class MyThreadSafeDictionary<T, J> : IDictionary<T, J>
{
      private object mutex = new object();
      private IDictionary<T, J> impl;

      public MyThreadSafeDictionary(IDictionary<T, J> impl)
      {
          this.impl = impl;
      }

      public void Add(T key, J value) 
      {
         lock(mutex) {
             impl.Add(key, value);
         }
      }

      // implement the other methods as for Add
}

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

Также обратите внимание, что объекты Dictionary не поддерживают смену ключей; единственный безопасный способ добиться желаемого - удалить существующую пару ключ/значение и добавить новый с обновленным ключом.

Ответ 6

Изменение значения - это запись и введение условия гонки.

Скажем, исходное значение mydict [5] = 42. Один поток обновляет mydict [5] до 112. Другой поток обновляет mydict [5] до 837.

Что должно значить значение mydict [5]? Порядок этих потоков важен в этом случае, то есть либо вам нужно убедиться, что порядок явный, либо что они не пишут.