为什么我的代码不会加速多线程Parallel.For循环?

时间:2017-07-20 19:23:16

标签: c# multithreading parallel-processing thread-safety thread-local

我尝试使用System.Threading.Tasks库将简单的顺序循环转换为并行计算循环。 代码编译,返回正确的结果,但它不会节省任何计算成本,否则需要更长的时间。

编辑:对不起,伙计们,我可能过分简化了问题并且做了一些错误。 要附加其他信息,我在i7-4700QM上运行代码,并在Grasshopper脚本中引用。 这是实际的代码。我也切换到非线程局部变量

public static class LineNet
{        
    public static List<Ray> SolveCpu(List<Speaker> sources, List<Receiver> targets, List<Panel> surfaces)
    {
        ConcurrentBag<Ray> rays = new ConcurrentBag<Ray>();
        for (int i = 0; i < sources.Count; i++)
        {
            Parallel.For(
                0,
                targets.Count,
                j =>
                {
                    Line path = new Line(sources[i].Position, targets[j].Position);
                    Ray ray = new Ray(path, i, j);
                    if (Utils.CheckObstacles(ray,surfaces))
                    {
                        rays.Add(ray);
                    }

                }
                );
        }
    }
}

Grasshopper实施只收集sources targetssurfaces,调用方法Solve并返回rays。 我知道将调度工作量分配到线程是昂贵的,但它是如此昂贵? 或者ConcurrentBag只是阻止并行计算?

另外,我的类是不可变的(?),但如果我使用公共List内核中止操作并抛出异常,是否有人能说出原因?

1 个答案:

答案 0 :(得分:2)

如果没有可靠地再现问题的好Minimal, Complete, and Verifiable code example,就无法提供明确的答案。您发布的代码甚至看起来不是真实代码的摘录,因为声明为方法的返回类型的类型与return语句实际返回的值不同。

但是,您发布的代码当然不能很好地使用Parallel.For()。您的Line构造函数相当昂贵,无法证明并行化创建项目的任务。要明确的是,这是唯一可能的胜利。

最后,您仍然需要将您创建的所有Line个实例聚合到一个列表中,因此为Parallel.For()任务创建的所有中间列表都只是纯粹的开销。并且聚合必然是序列化的(即,一次只有一个线程可以向result集合添加项目),并且以最糟糕的方式(每个线程只能在放弃锁定之前添加单个项目)而另一个线程有机会接受它。)

坦率地说,最好将每个本地List<T>存储在一个集合中,然后在Parallel.For()返回后在主线程中一次性聚合它们。并不是说这可能会使代码比直接非并行化实现更好。但至少它不太可能变得更糟。 :)

最重要的是,您似乎没有可以从并行化中受益的工作负载。如果您不这么认为,您需要以更清晰,更详细的方式解释该思想的基础。

  

如果我使用公共列表,内核中止操作并抛出异常,有人能说出原因吗?

您已经使用(看起来)List<T>作为每项任务的本地数据,事实上这应该没问题,因为任务不会共享其本地数据。

但是如果你问为什么如果你试图使用List<T>代替ConcurrentBag<T> result来获得异常,那么这完全是预期的。 List<T>类不是线程安全的,但Parallel.For()将允许它运行的每个任务与所有其他任务同时执行localFinally委托。所以你有多个线程都试图同时修改同一个非线程安全的集合。这是灾难的秘诀。你幸运的是你得到了例外;实际的行为是未定义的,并且您只是因为导致运行时异常而损坏数据结构。