C# 在Parallel.ForEach中使用哈希表?
我有一个Parallel.ForEach循环在体内运行一个密集的操作 该操作可以使用哈希表存储值,并且可以对其他连续循环项重复使用。我在密集操作完成后添加到哈希表中,下一个循环项可以在哈希表中查找并重用对象,而不是再次运行密集操作 但是,由于我使用的是Parallel.ForEach,因此存在一个不安全的问题,导致Hashtable.Add和ContainsKey(key)调用不同步,因为它们可能并行运行。引入锁可能会导致性能问题 以下是示例代码:C# 在Parallel.ForEach中使用哈希表?,c#,.net,parallel-extensions,task-parallel-library,C#,.net,Parallel Extensions,Task Parallel Library,我有一个Parallel.ForEach循环在体内运行一个密集的操作 该操作可以使用哈希表存储值,并且可以对其他连续循环项重复使用。我在密集操作完成后添加到哈希表中,下一个循环项可以在哈希表中查找并重用对象,而不是再次运行密集操作 但是,由于我使用的是Parallel.ForEach,因此存在一个不安全的问题,导致Hashtable.Add和ContainsKey(key)调用不同步,因为它们可能并行运行。引入锁可能会导致性能问题 以下是示例代码: Hashtable myTable = new
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和属性设置,可以处理这种情况。有吗?除了使用(或多或少显式的)锁(同步哈希表只是用锁覆盖所有方法),我看不到其他正确的选择 另一个选择是允许字典不同步。竞争条件不会损坏字典,它只需要代码进行一些多余的计算。配置代码以检查锁定或丢失的备忘录是否会产生更坏的影响。您正在寻找的。新的并发集合使用了显著改进的锁定机制,应该在并行算法中表现出色 编辑:结果可能如下所示:
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
} );
ConcurrentDictionary缓存=。。。;
Parallel.ForEach(items,(item,loopState)=>
{
K值;
如果(!cache.TryGetValue(item.Key,out值))
{
value=SomeIntensiveOperation();
cache.TryAdd(item.Key,value);
}
//做有价值的事
} );
警告词:如果
项中的元素不都具有唯一的项。键
,则该键可能会调用两次某些强度操作
。在本例中,键没有传递给SomeIntensiveOperation
,但这意味着“使用值做点什么”代码可以执行键/值A和键/值B对,并且只有一个结果会存储在缓存中(不一定是由SomeIntensiveOperation计算的第一个结果)。如果这是一个问题,您需要一个并行的懒惰工厂来处理它。此外,出于显而易见的原因,某些IntensifiveOperation应该是线程安全的。使用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;
}
检查我认为您需要的名称空间.NET Framework有两个读写器锁,ReaderWriterLockSlim和ReaderWriterLock。ReaderWriterLockSlim建议用于所有新开发。ReaderWriterLockSlim与ReaderWriterLock类似,但它简化了递归规则以及升级和降级锁状态的规则。ReaderWriterLockSlim避免了许多潜在死锁的情况。此外,ReaderWriterLockSlim的性能明显优于ReaderWriterLockSlim。“这个建议听起来不错,所以我更新了我的答案。对于那些感兴趣的人,请看一看这篇MSDN杂志文章:为什么这篇文章没有与HashTable.Synchronized()相同的问题
导致双线程竞争条件,其中两个线程都从TryGet
获取null
返回值,然后都计算myObj
并尝试添加它?这只意味着使对哈希表的访问成为原子的;它不会进行密钥冲突检测,但最坏的情况应该是效率较低的最后一次写入(>1个线程执行相同的工作。)也可能是Syncrhonized()使用的锁
是静态的,是为了支持兼容,而ReaderWriterLockSlim
显然已经更新,以通过不同的锁定模型避免许多旧的死锁情况。@AdamRalph:因为他正在使用TPL库,所以他已经在使用.net 4了。0@Adam&Yassir:没错,新系列的设计考虑到了并行LINQ。是的,谢谢你的支持答案和评论