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"
添加内容
我正在尝试使用 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"
添加内容