LINQ上的lock关键字Parallel.ForEach<>环

时间:2014-02-17 19:53:11

标签: c# .net linq parallel-processing parallel.foreach

这是一个概念性问题。我想知道我是否在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);
    }
});

fileLockObjectfileLocks的位置如下。

private static object fileLockObject = new object();
        private static Dictionary<string, object> fileLocks = new Dictionary<string, object>();

这项技术是否完全使循环不平行? 我想看看你对此的想法。

4 个答案:

答案 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中的所有工作)。

最重要的是,我认为你在这里做的事情没有什么大不了的。