8

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

4 回答 4

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如果不是所有元素都具有 unique item.Key,则SomeIntensiveOperation可能会为该键调用两次。在示例中,键没有传递给SomeIntensiveOperation,但这意味着“Do something with value”代码可以执行 key/valueA 和 key/valueB 对,并且只有一个结果会存储在缓存中(不一定是第一个一个由 SomeIntensiveOperation 计算)。如果这是一个问题,你需要一个并行的惰性工厂来处理这个问题。此外,出于显而易见的原因, SomeIntensiveOperation 应该是线程安全的。

于 2009-11-01T18:41:57.060 回答
4

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

于 2009-11-01T18:42:56.107 回答
3

使用 ReaderWriterLock,这对于具有大量读取和少量写入且持续时间短的工作具有良好的性能。您的问题似乎符合此规范。

所有读取操作都将快速运行并且无锁,唯一会阻塞任何人的时间是在发生写入时,并且该写入仅与将某些内容推入哈希表所需的时间一样长。

MSDN 上的 ReaderWriterLockSlim

我想我会扔掉一些代码......

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;
}
于 2009-11-01T18:42:08.607 回答
1

除了使用(或多或少显式)锁之外,我没有看到其他正确的选择(同步的哈希表只是用锁覆盖所有方法)。

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

于 2009-11-01T18:33:21.990 回答