Ответ 1
Словарь не является потокобезопасным вообще, независимо от того, добавляете ли вы к нему или нет - внутри него есть несколько внутренних структур, которые необходимо синхронизировать (особенно, когда внутренние hashbuckets меняются).
Вам либо нужно реализовать свою собственную блокировку вокруг любой операции на нем, либо если вы находитесь в .Net 4.0, вы можете использовать новый ConcurrentDictionary, который абсолютно фантастический, и который полностью потокобезопасен.
Другой вариант (обновление)
Тем не менее, есть еще один способ, который вы можете использовать, но для этого потребуется немного подстройки в зависимости от типа данных, которые вы вставляете в словарь, и гарантированы ли все ваши ключи уникальными:
Дайте каждому потоку собственный словарь, который он вставляет.
Когда каждый поток заканчивается, соедините все словари вместе и объедините их в более крупный; как вы обрабатываете дубликаты ключей, зависит от вас. Например, если вы кешируете списки элементов по ключу, вы можете просто объединить каждый список с одним ключом в один и поместить его в главный словарь.
Официальный ответ на вопрос: производительность (после того, как вы приняли)
Так как ваши комментарии говорят, вам нужна идея о лучшем методе (блокировка или слияние) для производительности и т.д. Я не могу сказать вам, что это будет; в конечном итоге его необходимо будет сравнить. Я посмотрю, могу ли я предложить некоторые рекомендации, хотя:)
Во-первых - если у вас есть представление о том, сколько элементов вам потребуется в Dictionar (y/ies), используйте конструктор (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 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
действительно медленный; то вы можете подумать о том, чтобы освободить блокировку, создав клонированный объект, представляющий слияние существующего и нового объекта, а затем повторно захватив блокировку. Однако вам нужен надежный способ проверки того, что состояние существующего объекта не изменилось между первой блокировкой и второй (для этого полезно использовать номер версии).