C#并行编程之数据并行Tasks.Parallel类
什么是数据并行
数据并行是并行编程中的一种模式,它的目的是对一个非常大的数据集进行并行处理。为了实现数据并行,可以将数据划分成多个部分,然后在多个处理器(或者CPU核心)上同时处理这些部分。每个处理器/核心都处理同一份代码,但是处理的数据不同。
Tasks.Parallel类
.NET Framework提供了Tasks.Parallel类,这个类可以让开发者轻松实现数据并行。这个类中的方法可以自动地将数据划分成多个小部分,并在多个处理器上并行执行。Tasks.Parallel类是.NET Framework 4中新增的一个功能,它为开发者提供了一种简单易用的方式,来并行处理大量数据。
基本用法
在Tasks.Parallel类中,有三个常用的方法,可以实现并行执行代码:
- Parallel.For(): 这个方法可以用来执行for循环中的代码,并行处理多个循环迭代。假设我们要并行计算数组a中每个元素的平方,我们可以这么做:
int[] a = new int[] { 1, 2, 3, 4, 5 };
Parallel.For(0, a.Length, i =>
{
a[i] = a[i] * a[i];
});
在这个例子中,我们使用Parallel.For方法并行执行了一个for循环,它计算了数组a中每个元素的平方。Parallel.For方法的三个参数分别是:循环的起始值、循环的结束值和一个Lambda表达式。Lambda表达式接收循环变量(这里是i),并执行循环体中的代码。
- Parallel.ForEach(): 这个方法是遍历集合中的元素,并对每个元素执行一段代码。假设我们要遍历一个字符串列表,并打印每个字符串的长度,我们可以这么做:
List<string> list = new List<string>() { "hello", "world", "goodbye", "moon" };
Parallel.ForEach(list, s =>
{
Console.WriteLine("{0} - {1}", s, s.Length);
});
在这个例子中,我们使用Parallel.ForEach方法并行遍历了一个字符串列表,对每个元素执行了一段Lambda表达式中的代码。
- Parallel.Invoke(): 这个方法可以让我们在多个处理器上并行执行多个方法。假设我们有两个计算密集型的方法A和B,我们可以这么做:
Parallel.Invoke(() => A(), () => B());
在这个例子中,我们使用Parallel.Invoke方法并行执行了两个方法A和B。
取消并行处理
Tasks.Parallel类还支持取消并行处理。如果在并行执行的过程中,用户试图中断程序的执行,我们可以使用CancellationTokenSource类和CancellationToken类来取消并行处理。
假设我们要对一个非常大的数组执行并行处理,但是用户在处理过程中想取消操作。我们可以这么做:
int[] a = new int[100000000];
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
try
{
Parallel.For(0, a.Length, po, i =>
{
a[i] = a[i] * a[i];
if (i > 1000 && po.ShouldExitCurrentIteration)
{
cts.Cancel();
}
});
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled.");
}
在这个例子中,我们使用CancellationTokenSource类和ParallelOptions类来实现取消操作。我们首先创建了一个CancellationTokenSource对象,并将它的Token属性赋值给了ParallelOptions对象的CancellationToken属性。然后,在循环体中,我们通过调用ParallelOptions对象的ShouldExitCurrentIteration属性来判断是否需要取消操作。如果循环已经执行了一定次数(这里是1000),就调用CancellationTokenSource对象的Cancel方法来取消操作。
多线程安全
并行编程当中,多个线程同时访问同一份数据,容易引发竞争状态(race condition)和死锁(deadlock)等问题。为了避免这些问题,我们需要使用锁(lock)、互斥(Mutex)或信号量(Semaphore)等机制来保证多线程安全。
Tasks.Parallel类中的方法默认是线程安全的,因为每个线程都只访问自己的数据。但是,如果我们需要对共享资源进行访问和修改,就需要自己编写线程安全的代码,以避免竞争状态和死锁等问题。
总结
Tasks.Parallel类是.NET Framework中实现数据并行的重要组件。通过使用这个类中的方法,开发者可以轻松地实现高效的并行处理,提高应用程序的性能。同时,我们需要自己编写线程安全的代码,以确保多线程操作的正确性。
示例
遍历并发下载多个网页
下面的示例展示了如何并行地从多个网页中下载HTML代码,并将结果存储到本地磁盘上:
string[] urls = new string[] { "http://www.baidu.com", "http://www.google.com", "http://www.bing.com" };
Parallel.ForEach(urls, url =>
{
WebClient client = new WebClient();
string html = client.DownloadString(url);
string fileName = Path.GetFileName(url) + ".html";
File.WriteAllText(fileName, html);
});
在这个示例中,我们使用了Parallel.ForEach方法,并行地遍历了一个字符串数组。对于每个字符串,我们创建了一个WebClient对象,并使用它来下载相应网页的HTML代码。最后,我们将HTML代码保存到本地磁盘上,文件名为网页的URL地址。
并行排序一个大数组
下面的示例展示了如何并行地对一个非常大的数组进行排序:
int[] a = new int[1000000];
Random rand = new Random();
for (int i = 0; i < a.Length; i++)
{
a[i] = rand.Next(100);
}
Parallel.ForEach(Partitioner.Create(0, a.Length), range =>
{
Array.Sort(a, range.Item1, range.Item2 - range.Item1);
});
Array.Sort(a);
在这个示例中,我们首先创建了一个随机数组a,数组长度为100万。然后,使用Parallel.ForEach方法并行遍历了一个分区(Partitioner.Create),并调用了Array.Sort方法对每个分区中的数据进行排序。最后,我们再次调用Array.Sort方法对整个数组进行排序,以确保数据的正确顺序。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#并行编程之数据并行Tasks.Parallel类 - Python技术站