在Parallel.ForEach中使用哈希表?

时间:2009-11-01 18:20:06

标签: c# .net parallel-extensions task-parallel-library

我有一个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
}

TPL库中必须有一些API,属性设置,可以处理这种情况。有吗?

4 个答案:

答案 0 :(得分:18)

您正在寻找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,但这意味着“执行有价值的东西”代码可以执行key / valueA和key / valueB对,并且只有一个结果将存储在缓存中(不一定是由SomeIntensiveOperation计算的第一个)。你需要一个并行的懒惰工厂来处理这个,如果这是一个问题。此外,由于显而易见的原因,SomeIntensiveOperation应该是线程安全的。

答案 1 :(得分:4)

检查我认为您需要的System.Collections.Concurrent命名空间ConcurrentDictionary

答案 2 :(得分:3)

使用ReaderWriterLock,这对于具有许多读取和很少写入的工作具有良好的性能。您的问题似乎符合此规范。

所有读取操作都将快速运行并锁定,任何人被阻止的唯一时间就是写入时,并且只有在Hashtable中推送内容时才会写入。

ReaderWriterLockSlim on 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;
}

答案 3 :(得分:1)

我看到没有其他正确的选择,而不是使用(或多或少显式)锁(同步Hashtable只是覆盖所有带锁的方法)。

另一种选择可能是允许字典不同步。竞争条件不会破坏字典,它只需要代码进行一些多余的计算。配置代码以检查锁定或丢失的memoization是否具有更差的影响。