这更多的是一个概念性问题。我想知道我是否在
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>();
这种技术是否完全使循环不平行? 我想看看你对此的想法。
这意味着
lock
内部的所有工作都不能并行完成。是的,这极大地损害了这里的性能。由于整个主体并未全部锁定(并且锁定在同一个对象上),因此这里仍然存在一些并行化。您获得的并行化是否增加了足够的好处以超越管理线程和围绕锁同步所带来的开销,您实际上只需要使用特定数据来测试自己。
也就是说,看起来您正在做的事情(至少在第一个锁定块中,这是我在每个线程锁定同一个对象时更关心的块)正在锁定对
Dictionary
的访问。您可以改为使用 ConcurrentDictionary
,它是专门设计用于多线程使用的,并且可以最大限度地减少需要完成的同步量。
如果我使用锁...如果这会消除并行 foreach 循环的好处。
按比例。当
RecursiveBinUpdate()
是一大块工作(并且独立)时,它仍然会得到回报。锁定部分可能小于 1%,或 99%。查找适用于此的阿姆达尔定律。
但更糟糕的是,您的代码不是线程安全的。从您对
fileLocks
的 2 次操作来看,只有第一个实际上是 inside 一把锁。
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 一起使用。
循环的“锁定”部分最终将串行运行。如果
RecursiveBinUpdate
函数是大部分工作,可能会有一些收获,但如果你能提前弄清楚如何处理锁生成,那就更好了。
谈到锁时,PLINQ/TPL 线程必须等待获取访问权限的方式没有区别。因此,在您的情况下,它只会使循环在您锁定的那些区域中不并行,并且这些锁之外的任何工作仍将并行执行(即
RecursiveBinUpdate
中的所有工作)。
底线,我认为你在这里所做的事情没有什么实质性问题。