这是一个概念性问题。我想知道我是否在lock
循环中使用了Parallel.ForEach<>
,如果这会消除并行foreach
循环的好处。
以下是我看过它的一些示例代码。
Parallel.ForEach<KeyValuePair<string, XElement>>(binReferences.KeyValuePairs, reference =>
{
lock (fileLockObject)
{
if (fileLocks.ContainsKey(reference.Key) == false)
{
fileLocks.Add(reference.Key, new object());
}
}
RecursiveBinUpdate(reference.Value, testPath, reference.Key, maxRecursionCount, ref recursionCount);
lock (fileLocks[reference.Key])
{
reference.Value.Document.Save(reference.Key);
}
});
fileLockObject
和fileLocks
的位置如下。
private static object fileLockObject = new object();
private static Dictionary<string, object> fileLocks = new Dictionary<string, object>();
这项技术是否完全使循环不平行? 我想看看你对此的想法。
答案 0 :(得分:3)
这意味着lock
内部的所有工作都无法并行完成。这在很大程度上损害了这里的表现,是的。由于整个主体并非全部锁定(并锁定在同一个对象上),因此仍然存在一些并行化。您所获得的并行化是否会增加足够的好处,以超越管理线程和围绕锁定同步所带来的开销,这是您真正需要使用特定数据进行自我测试的。
那就是说,看起来你正在做的事情(至少在第一个锁定的块中,这是我在每个线程上更关心的是锁定在同一个对象上)是锁定对{的访问权限{1}}。您可以改为使用Dictionary
,它专门设计用于从多个线程中使用,并最大限度地减少需要完成的同步量。
答案 1 :(得分:2)
如果我使用了锁......如果这样可以消除并行前进的好处。
按比例。当RecursiveBinUpdate()
是一大块工作(并且是独立的)时,它仍然会得到回报。锁定部分可小于1%或99%。查看Amdahls法律,这适用于此。
但更糟糕的是,您的代码不是线程安全的。在fileLocks
上的2个操作中,只有第一个实际上是里面一个锁。
lock (fileLockObject)
{
if (fileLocks.ContainsKey(reference.Key) == false)
{
...
}
}
和
lock (fileLocks[reference.Key]) // this access to fileLocks[] is not protected
将第二部分更改为:
lock (fileLockObject)
{
reference.Value.Document.Save(reference.Key);
}
并且使用ref recursionCount
作为参数看起来也很可疑。它可能适用于Interlocked.Increment。
答案 2 :(得分:0)
循环的“锁定”部分将以串行方式运行。如果RecursiveBinUpdate
函数是大部分工作,可能会有一些好处,但如果你能够提前知道如何处理锁生成会更好。
答案 3 :(得分:0)
当谈到锁定时,PLINQ / TPL线程必须等待获取访问权限的方式没有区别。因此,在您的情况下,它只会使您在锁定的那些区域中的循环不平行,并且这些锁之外的任何工作仍将并行执行(即RecursiveBinUpdate
中的所有工作)。
最重要的是,我认为你在这里做的事情没有什么大不了的。