Использование хеш-таблицы внутри Parallel.ForEach? - PullRequest
7 голосов
/ 01 ноября 2009

У меня есть цикл Parallel.ForEach, выполняющий интенсивную операцию внутри тела.

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

Однако, поскольку я использую 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
}

Должен быть некоторый API, параметр Property внутри библиотеки TPL, который мог бы справиться с этим сценарием. Есть ли?

Ответы [ 4 ]

18 голосов
/ 01 ноября 2009

Вы ищете 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, и только один результат будет сохранен в кэше обязательно первый, рассчитанный SomeIntensiveOperation либо). Вам понадобится параллельная ленивая фабрика для обработки , если это проблема. Кроме того, по понятным причинам SomeIntensiveOperation должен быть потокобезопасным.

4 голосов
/ 01 ноября 2009

проверьте пространство имен System.Collections.Concurrent . Мне кажется, вам нужно ConcurrentDictionary

3 голосов
/ 01 ноября 2009

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

Все операции чтения будут выполняться быстро и без блокировок. Единственный раз, когда кто-либо будет заблокирован, - это когда происходит запись, и эта запись занимает только столько времени, сколько необходимо для помещения чего-либо в Hashtable.

ReaderWriterLockSlim на MSDN

Думаю, я скину немного кода ...

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;
}
1 голос
/ 01 ноября 2009

Я не вижу другого правильного выбора, кроме как использовать (более или менее явные) блокировки (синхронизированный Hashtable просто переопределяет все методы с блокировками).

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

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