Потокобезопасный словарь. Добавить - PullRequest
16 голосов
/ 01 апреля 2011

Безопасна ли Dictionary.Add() нить, когда вы только вставляете?

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

Я получил это исключение при добавлении нового ключа:

Exception Source:    mscorlib
Exception Type: System.IndexOutOfRangeException
Exception Message:   Index was outside the bounds of the array.
Exception Target Site: Insert

Хотя это довольно редко. Я знаю, что Dictionary не является потокобезопасным, хотя я думал, что только вызов .Add не вызовет проблем.

Ответы [ 2 ]

25 голосов
/ 01 апреля 2011

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

Вы должны либо внедрить собственную блокировку для любой операции над ней, либо, если вы находитесь в .Net 4.0, вы можете использовать новый ConcurrentDictionary - который является абсолютно фантастическим - и который полностью потоков-safe.

Другой вариант (обновление)

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

Дайте каждому потоку свой собственный частный словарь, в который он будет вставлять.

Когда каждый поток завершится, соберите все словари вместе и объедините ихв больший;как вы обрабатываете дубликаты ключей, зависит только от вас.Например, если вы кэшируете списки элементов по ключу, то вы можете просто объединить каждый список с одним и тем же ключом в один и поместить его в основной словарь.

Официальный ответ re: performance (после того, как вы приняли)

Итак, как говорится в ваших комментариях, вам нужна идея наилучшего метода (блокировка или слияние) для производительности и т. Д. Я не могу сказать вам, что это будет;в конечном итоге это необходимо будет сравнить.Я посмотрю, смогу ли я предложить какое-нибудь руководство, хотя:)

Во-первых - если вы знаете, сколько элементов в конечном итоге понадобится вашему Dictionar (s), используйте конструктор (int), чтобы минимизировать изменение размера.

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

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

Если в случае, когда ключ уже существует, необходимо принять решение на уровне потока, тогда вам понадобится конструкция lock () {}.

По словарю это обычно принимает следующую форму:

readonly object locker = new object();
Dictionary<string, IFoo> dictionary = new Dictionary<string, IFoo>();

void threadfunc()
{
  while(work_to_do)
  {
    //get the object outside the lock
    //be optimistic - expect to add; and handle the clash as a 
    //special case
    IFoo nextObj = GetNextObject(); //let's say that an IFoo has a .Name
    IFoo existing = null;
    lock(locker)
    {
      //TryGetValue is a god-send for this kind of stuff
      if(!dictionary.TryGetValue(nextObj.Name, out existing))
        dictionary[nextObject.Name] = nextObj;
      else
        MergeOperation(existing, nextObject);
    }
  }
}

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

3 голосов
/ 01 апреля 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...