并行编程与PLINQ-数据并行

为了简化开发,.NET 4.0 特别提供一个并行编程库System.Threading.Tasks,它可以简化并行开发,你无需直接跟线程或线程池打交道,就可以简单建立多线程应用程序。此外,.NET还提供了新的一组扩展方法PLINQ,它具有自动分析查询功能,如果并行查询能提高系统效率,则同时运行,如果查询未能从并行查询中受益,则按原顺序查询。下面将详细介绍并行操作的方式。

泛型委托

使用并行编程可以同时操作多个委托,在介绍并行编程前先简单介绍一下两个泛型委托System.Func<>与System.Action<>。

Func<>是一个能接受多个参数和一个返回值的泛型委托,它能接受0个到4个输入参数, 其中 T1,T2,T3,T4 代表自定的输入类型,TResult为自定义的返回值。
public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 arg1)
public delegate TResult Func<T1,T2, TResult>(T1 arg1,T2 arg2)
public delegate TResult Func<T1,T2, T3, TResult>(T1 arg1,T2 arg2,T3 arg3)
public delegate TResult Func<T1,T2, T3, ,T4, TResult>(T1 arg1,T2 arg2,T3 arg3,T4 arg4)

Action<>与Func<>十分相似,不同在于Action<>的返回值为void,Action能接受1~16个参数
public delegate void Action<T1>()
public delegate void Action<T1,T2>(T1 arg1,T2 arg2)
public delegate void Action<T1,T2, T3>(T1 arg1,T2 arg2, T3 arg3)
.............
public delegate void Action<T1,T2, T3, ,T4, ...... ,T16>(T1 arg1,T2 arg2,T3 arg3,T4 arg4,...... ,T16 arg16)

任务并行库(TPL)

System.Threading.Tasks中的类被统称为任务并行库(Task Parallel Library,TPL),TPL使用CLR线程池把工作分配到CPU,并能自动处理工作分区、线程调度、取消支持、状态管理以及其他低级别的细节操作,极大地简化了多线程的开发。

TPL包括常用的数据并行与任务并行两种执行方式:

数据并行

数据并行的核心类就是System.Threading.Tasks.Parallel,它包含两个静态方法 Parallel.For 与 Parallel.ForEach, 使用方式与for、foreach相仿。通过这两个方法可以并行处理System.Func<>、System.Action<>委托。

假设使用单线程方式查询3个Person对象,需要用时大约6秒,在使用并行方式,只需使用2秒就能完成查询,而且能够避开Thread的繁琐处理。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //设置最大线程数
 6             ThreadPool.SetMaxThreads(1000, 1000);
 7             //并行查询
 8             Parallel.For(0, 3,n =>
 9                 {
10                     Thread.Sleep(2000);  //模拟查询
11                     ThreadPoolMessage(GetPersonList()[n]);
12                 });
13             Console.ReadKey();
14         }
15 
16         //模拟源数据
17         static IList<Person> GetPersonList()
18         {
19             var personList = new List<Person>();
20 
21             var person1 = new Person();
22             person1.ID = 1;
23             person1.Name = "Leslie";
24             person1.Age = 30;
25             personList.Add(person1);
26             ...........
27             return personList;
28         }
29 
30         //显示线程池现状
31         static void ThreadPoolMessage(Person person)
32         {
33             int a, b;
34             ThreadPool.GetAvailableThreads(out a, out b);
35             string message = string.Format("Person  ID:{0} Name:{1} Age:{2}\n" +
36                   "  CurrentThreadId is {3}\n  WorkerThreads is:{4}" +
37                   "  CompletionPortThreads is :{5}\n",
38                   person.ID, person.Name, person.Age,
39                   Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
40 
41             Console.WriteLine(message);
42         }
43     }

若想停止操作,可以利用ParallelLoopState参数,下面以ForEach作为例子。
public static ParallelLoopResult ForEach<TSource>( IEnumerable<TSource> source, Action<TSource, ParallelLoopState> action)
其中source为数据集,在Action<TSource,ParallelLoopState>委托的ParallelLoopState参数当中包含有Break()和 Stop()两个方法都可以使迭代停止。Break的使用跟传统for里面的使用方式相似,但因为处于并行处理当中,使用Break并不能保证所有运行能立即停止,在当前迭代之前的迭代会继续执行。若想立即停止操作,可以使用Stop方法,它能保证立即终止所有的操作,无论它们是处于当前迭代的之前还是之后。

 1     class Program
 2     {
 3          static void Main(string[] args)
 4          {
 5              //设置最大线程数
 6              ThreadPool.SetMaxThreads(1000, 1000);
 7  
 8              //并行查询
 9              Parallel.ForEach(GetPersonList(), (person, state) =>
10                  {
11                      if (person.ID == 2)
12                          state.Stop();
13                      ThreadPoolMessage(person);
14                  });
15              Console.ReadKey();
16          }
17  
18          //模拟源数据
19          static IList<Person> GetPersonList()
20          {
21              var personList = new List<Person>();
22  
23              var person1 = new Person();
24              person1.ID = 1;
25              person1.Name = "Leslie";
26              person1.Age = 30;
27              personList.Add(person1);
28              ..........
29              return personList;
30          }
31  
32          //显示线程池现状
33          static void ThreadPoolMessage(Person person)
34          {
35              int a, b;
36              ThreadPool.GetAvailableThreads(out a, out b);
37              string message = string.Format("Person  ID:{0} Name:{1} Age:{2}\n" +
38                    "  CurrentThreadId is {3}\n  WorkerThreads is:{4}" +
39                    "  CompletionPortThreads is :{5}\n",
40                    person.ID, person.Name, person.Age,
41                    Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
42  
43              Console.WriteLine(message);
44          }
45      }

当要在多个线程中调用本地变量,可以使用以下方法:
public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<Of TSource>, Func<Of TLocal>, Func<Of TSource,ParallelLoopState,TLocal,TLocal>, Action<Of TLocal>)
其中第一个参数为数据集;
第二个参数是一个Func委托,用于在每个线程执行前进行初始化;
第 三个参数是委托Func<Of T1,T2,T3,TResult>,它能对数据集的每个成员进行迭代,当中T1是数据集的成员,T2是一个ParallelLoopState对 象,它可以控制迭代的状态,T3是线程中的本地变量;
第四个参数是一个Action委托,用于对每个线程的最终状态进行最终操作。

在以下例子中,使用ForEach计算多个Order的总体价格。在ForEach方法中,首先把参数初始化为0f,然后用把同一个Order的多个OrderItem价格进行累加,计算出Order的价格,最后把多个Order的价格进行累加,计算出多个Order的总体价格。

 1     public class Order
 2     {
 3         public int ID;
 4         public float Price;
 5     }
 6 
 7     public class OrderItem
 8     {
 9         public int ID;
10         public string Goods;
11         public int OrderID;
12         public float Price;
13         public int Count;
14     }
15 
16     class Program
17     {
18         static void Main(string[] args)
19         {
20             //设置最大线程数
21             ThreadPool.SetMaxThreads(1000, 1000);
22             float totalPrice = 0f;
23             //并行查询
24             var parallelResult = Parallel.ForEach(GetOrderList(),
25                      () => 0f,   //把参数初始值设为0
26                      (order, state, orderPrice) =>
27                      {
28                          //计算单个Order的价格
29                          orderPrice = GetOrderItem().Where(item => item.OrderID == order.ID)
30                               .Sum(item => item.Price * item.Count);
31                          order.Price = orderPrice;
32                          ThreadPoolMessage(order);
33                          
34                          return orderPrice;
35                      },
36                     (finallyPrice) =>
37                     {
38                         totalPrice += finallyPrice;//计算多个Order的总体价格
39                     }
40                 );
41             
42             while (!parallelResult.IsCompleted)
43                 Console.WriteLine("Doing Work!");
44 
45             Console.WriteLine("Total Price is:" + totalPrice);
46             Console.ReadKey();
47         }
48         //虚拟数据
49         static IList<Order> GetOrderList()
50         {
51             IList<Order> orderList = new List<Order>();
52             Order order1 = new Order();
53             order1.ID = 1;
54             orderList.Add(order1);
55             ............
56             return orderList;
57         }
58         //虚拟数据
59         static IList<OrderItem> GetOrderItem()
60         {
61             IList<OrderItem> itemList = new List<OrderItem>();
62 
63             OrderItem orderItem1 = new OrderItem();
64             orderItem1.ID = 1;
65             orderItem1.Goods = "iPhone 4S";
66             orderItem1.Price = 6700;
67             orderItem1.Count = 2;
68             orderItem1.OrderID = 1;
69             itemList.Add(orderItem1);
70             ...........
71             return itemList;
72         }
73 
74         //显示线程池现状
75         static void ThreadPoolMessage(Order order)
76         {
77             int a, b;
78             ThreadPool.GetAvailableThreads(out a, out b);
79             string message = string.Format("OrderID:{0}  OrderPrice:{1}\n" +
80                   "  CurrentThreadId is {2}\n  WorkerThreads is:{3}" +
81                   "  CompletionPortThreads is:{4}\n",
82                   order.ID, order.Price,
83                   Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
84 
85             Console.WriteLine(message);
86         }
87     }



你可能感兴趣的:(并行编程与PLINQ-数据并行)