详细的.Net并行编程高级教程--Parallel

转自:http://developer.51cto.com/art/201510/493687.htm

详细的.Net并行编程高级教程–Parallel
一直觉得自己对并发了解不够深入,特别是看了《代码整洁之道》觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准。而且在《失控》这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物。人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情。所以好奇心驱使下学习并发。便有了此文。
作者:Stoneniqiu来源:博客园|2015-10-13 09:18 收藏 分享
一直觉得自己对并发了解不够深入,特别是看了《代码整洁之道》觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准。而且在《失控》这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物。人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情。所以好奇心驱使下学习并发。便有了此文。
一、理解硬件线程和软件线程
多核处理器带有一个以上的物理内核–物理内核是真正的独立处理单元,多个物理内核使得多条指令能够同时并行运行。硬件线程也称为逻辑内核,一个物理内 核可以使用超线程技术提供多个硬件线程。所以一个硬件线程并不代表一个物理内核;Windows中每个运行的程序都是一个进程,每一个进程都会创建并运行 一个或多个线程,这些线程称为软件线程。硬件线程就像是一条泳道,而软件线程就是在其中游泳的人。
二、并行场合
.Net Framework4 引入了新的Task Parallel Library(任务并行库,TPL),它支持数据并行、任务并行和流水线。让开发人员应付不同的并行场合。
数据并行:有大量数据需要处理,并且必须对每一份数据执行同样的操作。比如通过256bit的密钥对100个Unicode字符串进行AES算法加密。
任务并行:通过任务并发运行不同的操作。例如生成文件散列码,加密字符串,创建缩略图。
流水线:这是任务并行和数据并行的结合体。
TPL引入了System.Threading.Tasks ,主类是Task,这个类表示一个异步的并发的操作,然而我们不一定要使用Task类的实例,可以使用Parallel静态类。它提供了 Parallel.Invoke, Parallel.For Parallel.Forecah 三个方法。
三、Parallel.Invoke
试图让很多方法并行运行的最简单的方法就是使用Parallel类的Invoke方法。例如有四个方法:
WatchMovie
HaveDinner
ReadBook
WriteBlog
通过下面的代码就可以使用并行。
System.Threading.Tasks.Parallel.Invoke(WatchMovie, HaveDinner, ReadBook, WriteBlog);
这段代码会创建指向每一个方法的委托。Invoke方法接受一个Action的参数组。
1
public static void Invoke(params Action[] actions);
用lambda表达式或匿名委托可以达到同样的效果。
System.Threading.Tasks.Parallel.Invoke(() => WatchMovie(), () => HaveDinner(), () => ReadBook(), delegate() { WriteBlog(); });
1.没有特定的执行顺序。
Parallel.Invoke方法只有在4个方法全部完成之后才会返回。它至少需要4个硬件线程才足以让这4个方法并发运行。但并不保证这4个方法能够同时启动运行,如果一个或者多个内核处于繁忙状态,那么底层的调度逻辑可能会延迟某些方法的初始化执行。

给方法加上延时,就可以看到必须等待最长的方法执行完成才回到主方法。
static void Main(string[] args)
{
System.Threading.Tasks.Parallel.Invoke(WatchMovie, HaveDinner, ReadBook,
WriteBlog);
Console.WriteLine(“执行完成”);
Console.ReadKey();
}

    static void WatchMovie() 
    { 
        Thread.Sleep(5000); 
        Console.WriteLine("看电影"); 
    } 
    static void HaveDinner() 
    { 
        Thread.Sleep(1000); 
        Console.WriteLine("吃晚饭"); 
    } 
    static void ReadBook() 
    { 
        Thread.Sleep(2000); 
        Console.WriteLine("读书"); 
    } 
    static void WriteBlog() 
    { 
        Thread.Sleep(3000); 
        Console.WriteLine("写博客"); 
    } 

这样会造成很多逻辑内核处于长时间闲置状态。
四、Parallel.For
Parallel.For为固定数目的独立For循环迭代提供了负载均衡 (即将工作分发到不同的任务中执行,这样所有的任务在大部分时间都可以保持繁忙) 的并行执行。从而能尽可能地充分利用所有的可用的内核。
我们比较下下面两个方法,一个使用For循环,一个使用Parallel.For 都是生成密钥在转换为十六进制字符串。
private static void GenerateAESKeys()
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < NUM_AES_KEYS; i++)
{
var aesM = new AesManaged();
aesM.GenerateKey();
byte[] result = aesM.Key;
string hexStr = ConverToHexString(result);
}
Console.WriteLine(“AES:”+sw.Elapsed.ToString());
}

private static void ParallelGenerateAESKeys()
{
var sw = Stopwatch.StartNew();
System.Threading.Tasks.Parallel.For(1, NUM_AES_KEYS + 1, (int i) =>
{
var aesM = new AesManaged();
aesM.GenerateKey();
byte[] result = aesM.Key;
string hexStr = ConverToHexString(result);
});

        Console.WriteLine("Parallel_AES:" + sw.Elapsed.ToString()); 
    } 

复制代码
private static int NUM_AES_KEYS = 100000;
static void Main(string[] args)
{
Console.WriteLine(“执行”+NUM_AES_KEYS+”次:”); GenerateAESKeys();
ParallelGenerateAESKeys();
Console.ReadKey();
}
复制代码

执行1000000次

这里并行的时间是串行的一半。
五、Parallel.ForEach
在Parallel.For中,有时候对既有循环进行优化可能会是一个非常复杂的任务。Parallel.ForEach为固定数目的独立For Each循环迭代提供了负载均衡的并行执行,且支持自定义分区器,让使用者可以完全掌握数据分发。实质就是将所有要处理的数据区分为多个部分,然后并行运 行这些串行循环。
修改上面的代码:
System.Threading.Tasks.Parallel.ForEach(Partitioner.Create(1, NUM_AES_KEYS + 1), range =>
{
var aesM = new AesManaged();
Console.WriteLine(“AES Range({0},{1} 循环开始时间:{2})”,range.Item1,range.Item2,DateTime.Now.TimeOfDay);

            for (int i = range.Item1; i < range.Item2; i++) 
            { 
                aesM.GenerateKey(); 
                byte[] result = aesM.Key; 
                string hexStr = ConverToHexString(result); 
            } 
            Console.WriteLine("AES:"+sw.Elapsed.ToString()); 
        }); 

从执行结果可以看出,分了13个段执行的。

第二次执行还是13个段。速度上稍微有差异。开始没有指定分区数,Partitioner.Create使用的是内置默认值。

而且我们发现这些分区并不是同时执行的,大致是分了三个时间段执行。而且执行顺序是不同的。总的时间和Parallel.For的方法差不多。
public static ParallelLoopResult ForEach(Partitioner source, Action body)
Parallel.ForEach方法定义了source和Body两个参数。source是指分区器。提供了分解为多个分区的数据源。body是 要调用的委托。它接受每一个已定义的分区作为参数。一共有20多个重载,在上面的例子中,分区的类型为Tuple

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值