您的位置:首页 > 编程语言

理解并行编程

2016-02-14 14:24 211 查看
  并行编程从业务实现的角度可分为数据并行与任务并行,也就是要解决的问题是以数据为核心还是以要处理的事情为核心。基于任务的并行编程模型TPL(任务并行库)是从业务角度实现的并行模型,它以System.Threading.Tasks命名空间下的Parallel类为实现核心类,优点是不需要我们考虑不同的硬件差异,只需要重点关注所实现的任务。

1.任务并行库TPL

  TPL主要包括数据并行和任务并行,无论是数据并行还是任务并行,都可以使用并行查询PLINQ提高数据查询的效率。数据并行是对数据集合中的元素同时执行相同的操作,实现方式主要是利用Parallel.For或Parallel.ForEach。任务并行是采用Parallel.Invoke方法来实现。并行查询是指并行实现LINQ查询,它与LINQ的主要区别就在于并行,它会利用多处理器去去同时执行查询,查询前会先对数据源进行分区,之后每个处理器开始执行其中一个小片段,这样就加快了查询的速度。

  TPL主要有以下优点,TPL编程模型使用CLR线程池执行多个任务,并能自动处理工作分区、线程调度和取消、状态管理以及其他低级别的细节操作;TPL更加智能性,它可以通过试探法来预判任务集并行运行是否有性能优势,当结果是没有性能优势时还会自动选择顺序运行;TPL还会动态地按比例调节并发程度,从而最有效地使用所有可用的处理器。对TPL进行一个总结就是,它让编程人员不必考虑多处理器如何并行工作以及合理调节等底层实现,只需要考虑业务逻辑就可以了。

  上面提到了使用数据并行时或PLINQ时,需要对数据源进行分区。主要实现类为System.Collections.Concurrent命名空间中提供的Partitioner类来实现,通过这个类来将数据源分成许多小片段。对于分区主要有4种方式:按范围分区,适用于已经知道长度的数据源,当多个线程并行处理数据片段时,每个线程接受唯一的开始和结束索引,并行处理时不会覆盖其他线程或被其他线程覆盖,这种分区的缺点是如果一个线程已执行完,它无法帮助那些未完成的线程;对于长度未知的集合则使用按区块分区,线程执行时会并行循环执行区块中一定数量的数据,执行完再检索其他区块。分区程序可确保分发所有元素,并且没有重复项,但是每次线程获取另一个区块时,这时会产生同步开销。一般情况下按区块分区比按范围分区速度快;TPL还支持动态数量的分区,即可以随时创建分区;编程人员也可以自定义分区程序,可从System.Collections.Concurrent.Partitioner<TSource>派生并重写虚方法。

2.Parallel.For和Parallel.ForEach

  在数据并行中,For和ForEach方法会自动对源集合进行分区,这样多个线程才可以并行执行。对这2个方法的理解可以是在for和foreach的基础上加了并行,要注意在多重循环中一般只对外部循环进行并行化,当内部循环执行的工作用时较少时使用内部并行会降低整体运行的性能。对于ForEach方法有我敲的2个小例子,一个是简单ForEach方法的使用,还有一个是按范围分区ForEach方法的使用。以下是ForEach的代码:

//Task.WaitAll
private void button5_Click(object sender, EventArgs e)
{
Func<object, int> func = (object obj) =>
{
int k = (int)obj;
return k + 1;
};
Task<int>[] tasks = new Task<int>[3];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = new Task<int>(func, i);
tasks[i].Start();
}
Task.WaitAll(tasks);
listBox1.Items.Add("所有任务都已经执行完了");
}
//Task.WaitAny
private void button4_Click(object sender, EventArgs e)
{
Action act = () =>
{
int i=1;
i++;
};
Task task = new Task(act);
task.Start();
Task.WaitAny(task);
listBox1.Items.Add("任务已经执行完了");
}


View Code
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: