C#.net 多线程

C#.net multithreading

我正在尝试使用 C#.net 在名为 Grasshopper(Rhino3D 的一部分)的包中优化一些数学运算。该操作非常简单,但必须执行的列表很大,而且可能会变得更大。

我在我的 C# 脚本中使用 Parallel.ForEach 和列表,我得到的最终结果数量低于预期。这很可能是由于 list.add 不是线程安全的(或者在我构建它的软件中不是线程安全的)。

  private void RunScript(double z, int x, List<double> y, ref object A)
  {
    List<double> temp = new List<double>();
    double r;
    System.Threading.Tasks.Parallel.ForEach(y, numb =>
      {
      r = Math.Pow((numb * x), z);
      temp.Add(r);
      });
    A = temp;

请帮我想出一个简单有效的方法 运行 使用 CPU 多线程(或者如果您对 GPU CUDA 有任何建议)对数百个值进行这种简单的数学运算。

我希望晦涩而具体的软件不会打扰您,因为据我所知,它的性能与普通 C#.Net/Python/VB.Net.

您猜对了,List<T> 不是线程安全的。您必须同步访问它的任何实例。

一个选项是在每个任务中简单地同步:

private void RunScript(double z, int x, List<double> y, ref object A)
{
    List<double> temp = new List<double>();
    object l = new object();
    System.Threading.Tasks.Parallel.ForEach(y, numb =>
    {
      double r = Math.Pow((numb * x), z);
      lock (l) temp.Add(r);
    });
    A = temp;
}

注意:您的代码中还有另一个错误。您在所有任务中共享相同的 r 变量,这可能导致相同的值被添加到结果中两次或更多次,而其他值被排除在外。我通过简单地将变量声明移动到用于 ForEach() 调用的匿名方法的主体来修复错误。


另一种选择是认识到您事先知道您将获得多少结果,因此可以简单地初始化一个足够大的数组来包含所有结果:

private void RunScript(double z, int x, List<double> y, ref object A)
{
    double[] results = new double[y.Count];
    System.Threading.Tasks.Parallel.For(0, y.Count, i =>
    {
      // read-only access of `y` is thread-safe:
      results[i] = Math.Pow((y[i] * x), z);
    });
    A = new List<double>(results);
}

没有两个线程会尝试访问 results 数组中的同一个元素,并且数组本身永远不会改变(即重新分配),因此这是完全线程安全的。

以上假定您确实需要一个 List<double> 作为输出对象。当然,如果一个数组令人满意,那么你可以直接将 results 赋值给 A 而不是将其传递给 List<T> 构造函数以在最后创建一个全新的对象。

这是另一个选项:

    private void RunScript(double z, int x, List<double> y, ref object A) {
        var temp = new System.Collections.Concurrent.BlockingCollection<double>();
        System.Threading.Tasks.Parallel.ForEach(y, numb => {
            double r = Math.Pow((numb * x), z);
            temp.Add(r);
        });
        A = temp; // if needed you can A = temp.ToList();
        }

Peter 很好地概述了您的代码存在的问题,我认为他建议的第二个函数可能是您的最佳选择。仍然很高兴看到替代方案并了解 .NET 框架包含并发安全集合。

一个更简单的解决方案可能是使用 .AsParallel() 并处理结果 ParallelEnumerable

private void RunScript(double z, int x, List<double> y, ref object A)
{
    A = y
        .AsParallel().AsOrdered()
        .Select(elem => Math.Pow((elem * x), z))
        .ToList();
}

非常感谢您的投入! 如果您对分析器输出感兴趣,如下所示:

Peter Duniho 第一个选项:330 毫秒

Peter Duniho 第二个选项:207 毫秒

Dweeberly 选项:335 毫秒

Mattias Buelens 选项:376 毫秒

这很奇怪,据说 .net 脚本必须 运行 在 Grasshopper 中更快(因为它是 .net)但是 none 您的解决方案击败了 python 129 毫秒的并行计算!

总之谢谢大家的详细解答!你很棒!

我也在考虑稍微改变一下输入。将数据拆分为单独的分支,在单独的线程上计算每个分支,然后在最后重新组合它们。然而,它的得分最差,为 531 毫秒。 我知道剧本不好,但我认为它很好地表达了我的想法,如果写得好可能会达到 success.No?

  private void RunScript(double z, int x, List<double> y, DataTree<double> u, ref object A)
  {
    System.Threading.Tasks.Task<double[]> th1 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(0).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th2 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(1).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th3 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(2).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th4 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(3).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th5 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(4).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th6 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(5).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th7 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(6).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th8 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(7).ToArray(), x, z));

    List<double> list = new List<double>();

    list.AddRange(th1.Result);
    list.AddRange(th2.Result);
    list.AddRange(th3.Result);
    list.AddRange(th4.Result);
    list.AddRange(th5.Result);
    list.AddRange(th6.Result);
    list.AddRange(th7.Result);
    list.AddRange(th8.Result);


    A = list;


  }

抱歉,我无法向 "using"

添加内容