【问题标题】:PLINQ ForAll broken in .NET 4.0 and 4.5PLINQ ForAll 在 .NET 4.0 和 4.5 中损坏
【发布时间】:2012-05-22 16:54:48
【问题描述】:

我正在尝试想出一种方法,以尽可能快的方式加速组合列表中包含的大量对象。希望利用 PLINQ 我尝试了它,但这不是一个线程安全的解决方案。我在 4.0 和 4.5 的 VS2010 和 VS11Beta 中进行了测试。这是我的示例应用程序。如果您在 1-500 之间更改 BlowUp(),它通常会起作用。 500 年后车轮脱离轨道。它会在多个地方失败。有谁知道解决这个问题的最快方法? (多维数组+PLINQ?)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PLinqBlowsUp
{
class Program
{
    static void Main(string[] args)
    {
        BlowUp(5000);
    }

    private static void BlowUp(int blowupNum)
    {
        try
        {
            var theExistingMasterListOfAllRowsOfData = new List<List<KeyValuePair<string, dynamic>>>();

            //Add some test data
            Enumerable.Range(0, blowupNum).AsParallel().ForAll(row => theExistingMasterListOfAllRowsOfData.Add(AddRowToMasterList(row)));


            var aNewRowOfData = new List<KeyValuePair<string, dynamic>>();
            //Add some test data
            var column = new KeyValuePair<string, dynamic>("Title", "MyTitle");
            aNewRowOfData.Add(column);

            var anotherNewRowOfData = new List<KeyValuePair<string, dynamic>>();
            //Add some test data
            var columnA = new KeyValuePair<string, dynamic>("Date", DateTime.Now);
            var columnB = new KeyValuePair<string, dynamic>("ImportantColumn", "ImportantData");
            var columnC = new KeyValuePair<string, dynamic>("VeryImportantColumn", "VeryImportantData");
            anotherNewRowOfData.Add(columnA);
            anotherNewRowOfData.Add(columnB);
            anotherNewRowOfData.Add(columnC);

            //Now the Problem
            aNewRowOfData.AsParallel().ForAll(anrod => theExistingMasterListOfAllRowsOfData.ForEach(temloarod => temloarod.Add(anrod)));
            anotherNewRowOfData.AsParallel().ForAll(anrod => theExistingMasterListOfAllRowsOfData.ForEach(temloarod => temloarod.Add(anrod)));

            //Test for number
            foreach (var masterRow in theExistingMasterListOfAllRowsOfData)
            {
                if (masterRow.Count != 7)
                    throw new Exception("BLOW UP!!!");
            }
        }
        catch (AggregateException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    private static List<KeyValuePair<string, dynamic>> AddRowToMasterList(int row)
    {
        var columnA = new KeyValuePair<string, dynamic>("FirstName", "John" + row.ToString());
        var columnB = new KeyValuePair<string, dynamic>("LastName", "Smith" + row.ToString());
        var columnC = new KeyValuePair<string, dynamic>("Ssn", 123456789 + (row*10));

        var list = new List<KeyValuePair<string, dynamic>>();
        list.Add(columnA);
        list.Add(columnB);
        list.Add(columnC);
        return list;
    }
}
}

【问题讨论】:

  • 采纳以下建议...目前正在尝试使用多维 KVP 数组的 ForAll 来为此类进程提供多核速度。
  • 谁支持这样的问题?它说“PLinq 的 ForAll 不起作用。”这很简单,不是真的,只是表明提问者的误解(这是合法的;没有人知道一切,但问题的当前形式很糟糕)。 ForAll 被破坏的可能性有多大,直到现在还没有人意识到?

标签: .net multithreading linq task plinq


【解决方案1】:

这与 PLinq 无关 - 将项目添加到 List&lt;T&gt; 根本不是线程安全的。一个会降低性能的可行解决方案是引入锁定。相反,您通常想要做的是投射到一个 new 集合作为您的 PLinq 语句的结果 - 像您所做的那样引入副作用并不符合 Linq / 函数式编程的精神,并且你可能会遇到麻烦(就像你一样)。

【讨论】:

  • ForAll 似乎还有更多内容。如果我不想一开始就影响我的列表,我就不会 ForEach/ForAll'ing 他们。只是让 ForAll 看起来对列表毫无意义(因为我必须想出一种通过锁定来限制它的方法)。但是做一个 .ForEach 来影响我的收藏完全符合 Linq 的精神。那么为什么不是 .ForAll?
【解决方案2】:

我发现了两个问题。

  • 您正在从多个线程调用theExistingMasterListOfAllRowsOfData 实例上的Add,而没有任何尝试同步对其的访问。
  • 您正在多个线程中对单个 List&lt;KeyValuePair&lt;string, dynamic&gt;&gt; 项目调用 Add,而没有任何尝试同步它们。

您可以使用lock 来保护Add 方法,或者改用ConcurrentBag。但是,这些选项都不是那么好。这里的问题是这种操作不能很好地并行化,因为所有线程最终都会竞争同一个锁。我高度怀疑即使是低锁定 ConcurrentBag 最终也会比您刚刚在 PLINQ 上启动并在主线程上完成所有操作时要慢。

【讨论】:

    【解决方案3】:

    PLinq 不能替代编写线程安全代码。您对theExistingMasterListOfAllRowsOfData 的访问不是线程安全的,因为线程池中的所有线程都在访问它。您可以尝试锁定它,这为我解决了问题:

    Enumerable.Range(0, blowupNum).AsParallel().ForAll(row => {
        lock (theExistingMasterListOfAllRowsOfData) {                 
            theExistingMasterListOfAllRowsOfData.Add(AddRowToMasterList(row));
        }
    });
    

    但是,锁定可能不是您所追求的,因为这会引入瓶颈。

    【讨论】:

    • 我想到了锁,但这首先扼杀了执行 ForAll() 的意义。
    • 当然,但重点是 PLink 只是另一种分配和使用多线程的方式。您必须了解编写多线程代码的所有陷阱和挑战,无论您是创建自己的线程、手动从线程池中拉取线程,还是利用 PLink。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-18
    • 2013-08-25
    • 2012-06-23
    • 2013-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多