Использование хеш-таблицы внутри Parallel.ForEach?
-
11-09-2019 - |
Вопрос
У меня есть цикл Parallel.ForEach, выполняющий интенсивную операцию внутри тела.
Операция может использовать хэш-таблицу для хранения значений и может быть повторно использована для других последовательных элементов цикла.Я добавляю в хеш-таблицу после завершения интенсивной операции, следующий элемент цикла может найти в хэш-таблице и повторно использовать объект вместо повторного запуска интенсивной операции.
Однако, поскольку я использую Parallel.ForEach, возникает небезопасная проблема, из-за которой вызовы Hashtable.Add и containsKey(key) не синхронизируются, поскольку они могут выполняться параллельно.Введение блокировок может вызвать проблемы с производительностью.
Вот пример кода:
Hashtable myTable = new Hashtable;
Parallel.ForEach(items, (item, loopState) =>
{
// If exists in myTable use it, else add to hashtable
if(myTable.ContainsKey(item.Key))
{
myObj = myTable[item.Key];
}
else
{
myObj = SomeIntensiveOperation();
myTable.Add(item.Key, myObj); // Issue is here : breaks with exc during runtime
}
// Do something with myObj
// some code here
}
В библиотеке TPL должен быть какой-то API, настройка свойства, которая могла бы справиться с этим сценарием.Есть?
Решение
Вы ищете System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>
.В новых параллельных коллекциях используются значительно улучшенные механизмы блокировки и они должны превосходно работать в параллельных алгоритмах.
Редактировать:Результат может выглядеть так:
ConcurrentDictionary<T,K> cache = ...;
Parallel.ForEach(items, (item, loopState) =>
{
K value;
if (!cache.TryGetValue(item.Key, out value))
{
value = SomeIntensiveOperation();
cache.TryAdd(item.Key, value);
}
// Do something with value
} );
Слово предупреждения: если элементы в items
не все имеют уникальные item.Key
, затем SomeIntensiveOperation
для получения этого ключа можно было позвонить дважды.В примере ключ не передается SomeIntensiveOperation
, но это означает, что код «Сделать что-нибудь со значением» может выполнять пары ключ/значениеA и ключ/значениеB, и в кэше будет сохранен только один результат (не обязательно первый, вычисленный SomeIntensivOperation).Чтобы справиться с этим, вам понадобится параллельная ленивая фабрика. если Это проблема.Кроме того, по очевидным причинам SomeIntensivOperation должен быть потокобезопасным.
Другие советы
проверить System.Collections.Concurrent пространство имен, я думаю, тебе нужно ConcurrentDictionary
Используйте ReaderWriterLock, он обеспечивает хорошую производительность при работе с большим количеством операций чтения и небольшим количеством операций записи, которые имеют короткую продолжительность.Ваша проблема, кажется, соответствует этой спецификации.
Все операции чтения будут выполняться быстро и без блокировок, единственный раз, когда кто-либо будет заблокирован, — это когда происходит запись, и эта запись длится ровно столько времени, сколько требуется, чтобы поместить что-то в хеш-таблицу.
Думаю, я скину немного кода...
ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
Hashtable myTable = new Hashtable();
Parallel.ForEach(items, (item, loopState) =>
{
cacheLock.EnterReadLock();
MyObject myObj = myTable.TryGet(item.Key);
cacheLock.ExitReadLock();
// If the object isn't cached, calculate it and cache it
if(myObj == null)
{
myObj = SomeIntensiveOperation();
cacheLock.EnterWriteLock();
try
{
myTable.Add(item.Key, myObj);
}
finally
{
cacheLock.ExitWriteLock();
}
}
// Do something with myObj
// some code here
}
static object TryGet(this Hashtable table, object key)
{
if(table.Contains(key))
return table[key]
else
return null;
}
Я не вижу другого правильного выбора, кроме использования (более или менее явных) блокировок (синхронизированная хэш-таблица просто переопределяет все методы с блокировками).
Другой вариант — разрешить рассинхронизацию словаря.Состояние гонки не повредит словарь, оно просто потребует от кода выполнения некоторых лишних вычислений.Профилируйте код, чтобы проверить, не приведет ли блокировка или отсутствие запоминания к худшим последствиям.