Вопрос

У меня есть цикл 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 в 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;
}

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top