2011-01-14 3 views
10

В контексте этого заявления,Разъяснение чтения и записи на C# Словарь

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

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

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

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

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

ответ

2

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

+2

Это действительно комментарий, а не ответ на вопрос. Пожалуйста, используйте «добавить комментарий», чтобы оставить отзыв для автора. – SliverNinja

+0

@SliverNinja, мне кажется, что в этом ответе рассматривается основной вопрос: «Будет ли операция, которая обновляет значение для существующего ключа в словаре, должна рассматриваться как читатель или писатель?». – binki

-1

Изменение значения происходит запись и вводит состояние гонки.

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

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

+1

То, что вы описываете, на самом деле не является условием гонки - так как хорошо, кто бы ни делал это последние победы, и это было бы одинаково, если бы вы заблокировали доступ. Состояние гонки было бы чем-то вроде: if (! Dict.ContainsKey (5)) dict (5, new Value()) теперь возникает потенциальная несогласованность. – Neil

+0

@Neil Я думаю, что это состояние гонки, просто не состояние гонки, с которым может справиться словарь. У меня уже есть код, чтобы гарантировать, что тип состояния гонки, упомянутый в ответе, не происходит. – Joshua

+3

@Joshua - условие гонки может возникать в словаре, который реализует «mydict [5] = X», но между условиями постановки Serinus 3 нет никаких условий гонки. Кто когда-либо устанавливает значение последних побед - это просто описание нормального программирования. Условие гонки происходит в том случае, когда вы устанавливаете состояние на основе предыдущего предварительного изучения состояния. Само по себе x = 1 не может генерировать условие гонки x ++ can. – Neil

0

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

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

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

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

+2

Большая часть документации, которую я прочитал, показала, что замок Reader/Writer имеет потрясающе низкую производительность, хуже, чем просто использование одного замка. Похоже, что ReaderWriterLockSlim должен быть тем, что вы используете, если он доступен или использовать что-то еще. http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx –

+0

@Erik -I согласен, но добавил бы: это всегда баланс. Это должно было зависеть от других затрат в смеси. Если количество чтения и записи примерно равно, то я бы посоветовал использовать только lock(), но если запись встречается редко, и обычно это, вероятно, блокировка писателя, вероятно, прекрасна. Когда они будут обновлены, будет легко перейти к Slim lock. Профилирование, когда у вас есть проблема, - лучший способ получить производительность. Очень часто люди проводят дни кодирования, чтобы сохранить миллисекунды за операцию. – Neil

+0

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

0

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

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

ICollection provides a member to synchronize access и ссылка остается в силе в рамках операций по увеличению/сокращению.

+0

Это интересный момент, если я объявляю словарь и insert ("Key1", "Hello"), тогда do directory ["Key1"] = "Это действительно очень длинная строка", не было бы необходимо перераспределить память для значения, или поскольку значение представляет собой oject, а память уже выделена для объекта, он сможет повторно использовать эту память? Я предполагаю, что многое будет зависеть от внутренних свойств объекта Dictionary. – Joshua

+0

Для словаря <строка, строка> базовым хранилищем является ICollection >. Тот факт, что вы уже вставили значение, означает, что запись была добавлена ​​в ICollection (расширяя ее содержимое, увеличивая при необходимости память). Содержимое - это просто структура, содержащая ссылку на два строковых объекта. Изменение строки значения словаря на что-то другое просто создает новую строку в памяти и обновляет ссылку KeyValuePair. Никаких изменений в размере хранилища IDictionary. –

+0

Кроме того, Insert введет исключение, если вы попытаетесь дважды вставить тот же ключ. Это может происходить легко в многопоточной среде, где блокировка не была должным образом реализована. Если вы хотите быть более безопасным, просто доступ к несуществующему ключу приведет к его созданию. Если он существует, он будет обновлен. Никаких исключений. Используйте Insert, если добавление одного и того же ключа является фактическим условием ошибки, которое вы хотите поймать. directory.Insert («Key1», «Hello»); versus directory ["Key1"] = "Hello"; –

0

Операция чтения - это все, что получает ключ или значение от 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 
} 

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

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

+0

есть ли причина сделать это, а не использовать ConcurrentDictionary? – mcmillab

3

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

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

class MutableValueHolder<T> 
{ 
    public T Value; 
} 

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

Смежные вопросы