.NET並行編程實踐(一:.NET並行計算基本介紹、並行循環使用模式)


閱讀目錄:

  • 1.開篇介紹
  • 2.NET並行計算基本介紹
  • 3.並行循環使用模式
    • 3.1並行For循環
    • 3.2並行ForEach循環
    • 3.3並行LINQ(PLINQ)

1】開篇介紹

最近這幾天在搗鼓並行計算,發現還是有很多值得分享的意義,因為我們現在很多人對它的理解還是有點不准確,包括我自己也是這么覺得,所以整理一些文章分享給在使用.NET並行計算的朋友和將要使用.NET並行計算的朋友;

NET並行編程推出已經有一段時間了,在一些項目代碼里也時不時會看見一些眼熟的並行計算代碼,作為熱愛技術的我們怎能視而不見呢,於是搗鼓了一番跟自己的理解恰恰相反,看似一段能提高處理速度的並行代碼為能起效果,跟直接使用手動創建的后台線程處理差不多,這不太符合我們對.NET並行的強大技術的理解,所以自己搞了點資料看看,實踐了一下,發現在使用.NET並行技術的時候需要注意一些細節,這些細節看代碼是看不出來的,所以我們看到別人這么用我們就模仿這么用,我們需要自己去驗證一下到底能提高多少處理速度和它的優勢在哪里;要不然效率上不去反而還低下,查看代碼也不能很好的斷定哪里出了問題,所以還是需要系統的學習總結才行;

現在的系統已經不在是以前桌面程序了,也不是簡單的WEB應用系統,而是大型的互聯網社區、電子商務等大型系統,具有高並發,大數據、SOA這些相關特性的復雜體系的綜合性開放平台;.NET作為市場占有率這么高的開發技術,有了一個很強大的並行處理技術,目的就是為了能在高並發的情況下提高處理效率,提高了單個並發的處理效率也就提高了總體的系統的吞吐量和並發數量,在單位時間內處理的數據量將提高不是一個系數兩個系數;一個處理我們提高了兩倍到三倍的時間,那么在並發1000萬的頂峰時時不時很客觀;

2】.NET並行計算基本介紹

既然是.NET並行計算,那么我們首先要弄清楚什么叫並行計算,與我們以前手動創建多線程的並行計算有何不同,好處在哪里;我們先來了解一下什么是並行計算,其實簡單形容就是將一個大的任務分解成多個小任務,然后讓這些小任務同時的進行處理,當然純屬自己個人理解,當然不是很全面,但是我們使用者來說足夠了;

在以前單個CPU的情況下只能靠提高CPU的時鍾頻率,但是畢竟是有極限的,所以現在基本上是多核CPU,個人筆記本都已經基本上是4核了,服務器的話都快上20了;在這樣一個有利的計算環境下,我們的程序在處理一個大的任務時為了提高處理速度需要手動的將它分解然后創建Thread來處理,在.NET中我們一般都會自己創建Thread來處理單個子任務,這大家都不陌生,但是我們面臨的問題就是不能很好的把握創建Thread的個數和一些參數的控制,畢竟.NET並行也是基於以前的Thread來寫的,如何在多線程之間控制參數,如何互斥的執行的線程順序等等問題,導致我們不能很好的使用Thread,所以這個時候.NET並行框架為我們提供了一個很好的並行開發平台,畢竟大環境就是多核時代;

下面我們將接觸.NET並行計算中的第一個使用模式,有很多並行計算場景,歸結起來是一系列使用模式;

3】並行循環模式

並行循環模式就是將一個大的循環任務分解成多個同時並行執行的小循環,這個模式很實用;我們大部分處理程序的邏輯都是在循環和判斷之間,並行循環模式可以適當的改善我們在操作大量循環邏輯的效率;

我們看一個簡單的例子,看到底提升了多少CPU利用率和執行時間;

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Threading.Tasks;
 4 using System.Diagnostics; 
 5 
 6 namespace ConsoleApplication1.Data
 7 {
 8     public class DataOperation
 9     {
10         private static List<Order> orders = new List<Order>(); 
11 
12         static DataOperation()
13         {
14             for (int i = 0; i < 9000000; i++)
15             {
16                 orders.Add(new Order() { Oid = Guid.NewGuid().ToString(), OName = "OrderName_" + i.ToString() });
17             }
18         } 
19 
20         public void Operation()
21         {
22             Console.WriteLine("Please write start keys:");
23             Console.ReadLine(); 
24 
25             Stopwatch watch = new Stopwatch();
26             watch.Start();
27             orders.ForEach(order =>
28             {
29                 order.IsSubmit = true;
30                 int count = 0;
31                 for (int i = 0; i < 2000; i++)
32                 {
33                     count++;
34                 }
35             });
36             watch.Stop();
37             Console.WriteLine(watch.ElapsedMilliseconds);
38         } 
39 
40         public void TaskOperation()
41         {
42             Console.WriteLine("Please write start keys:");
43             Console.ReadLine(); 
44 
45             Stopwatch watch = new Stopwatch();
46             watch.Start();
47             Parallel.ForEach(orders, order =>
48             {
49                 order.IsSubmit = true;
50                 int count = 0;
51                 for (int i = 0; i < 2000; i++)
52                 {
53                     count++;
54                 }
55             });
56             watch.Stop();
57             Console.WriteLine(watch.ElapsedMilliseconds);
58         }
59     }
60 } 
View Code

這里的代碼其實很簡單,在靜態構造函數中我初始化了九百萬條測試數據,其實就是Order類型的實例,這在我們實際應用中也很常見,只不過不是一次性的讀取這么多數據而已,但是處理的方式基本上差不多的;然后有兩個方法,一個是Operation,一個是TaskOperation,前者順序執行,后者並行執行;

在循環的內部我加上了一個2000的簡單空循環邏輯,為什么要這么做后面會解釋介紹(小循環並行模式不會提升性能反而會降低性能);這里是為了讓模擬場景更真實一點;

我們來看一下測試相關的數據:i5、4核測試環境,執行時間為42449毫秒,CPU使用率為25%左右,4核中只使用了1和3的,而其他的都屬於一般處理狀態;

圖1:

我們再來看一下使用並行計算后的相關數據:i5、4核測試環境,執行時間為19927毫秒,CPU利用率為100%,4核中全部到達頂峰;

圖2:

這一個簡單的測試例子,當然我只測試了兩三組數據,基本上並行計算的速度要快於單線程的處理速度的2.1倍以上,當然還有其他因素在里面這里就不仔細分析了,起到拋磚引玉的作用;

3.1】並行For循環

在使用for循環的時候有相應的Parallel方式使用for循環,我們直接看一下示例代碼,還是以上面的測試數據為例;

1 Parallel.For(0, orders.Count, index =>
2             {
3                 //
4             }); 
View Code

第一個參數是索引的開始,第二個參數是循環總數,第三個是執行體,參數是索引值;使用起來其實很簡單的;

3.2】並行ForEach循環

同樣ForEach也是很簡單的,還是使用上面的測試數據為例;

1 Parallel.ForEach(orders, order =>
2             {
3                 order.IsSubmit = true;
4                 int count = 0;
5                 for (int i = 0; i < 2000; i++)
6                 {
7                     count++;
8                 }
9             }); 
View Code

在Parallel類中有ForEach方法,第一個參數是迭代集合,第二個是每次迭代的item;

其實Parallel為我們封裝了一個簡單的調用入口,其實是依附於后台的Task框架的,因為我們常用的就是循環比較多,畢竟循環是任務的入口調用,所以我們使用並行循環的時候還是很方便的;

3.3】並行LINQ(PLINQ)

首先PLINQ是只針對Linq to Object的,所以不要誤以為它也可以使用於Linq to Provider,當然自己可以適當的封裝;現在LINQ的使用率已經很高了,我們在做對象相關的操作時基本上都在使用LINQ,很方便,特別是Select、Where非常的常用,所以.NET並行循環也在LINQ上進行了一個封裝,讓我們使用LINQ的時候很簡單的使用並行特性;

LINQ核心原理的文章:http://www.cnblogs.com/wangiqngpei557/category/421145.html

根據LINQ的相關原理,知道LINQ是一堆擴展方法的鏈式調用,PLINQ就是擴展方法的集合,位於System.Linq.ParallelEnumerable靜態類中,擴展於ParallelQuery<TSource>泛型類;

System.Linq.ParallelQuery<TSource>:

 1 using System.Collections;
 2 using System.Collections.Generic; 
 3 
 4 namespace System.Linq
 5 {
 6     // 摘要:
 7     //     表示並行序列。
 8     //
 9     // 類型參數:
10     //   TSource:
11     //     源序列中的元素的類型。
12     public class ParallelQuery<TSource> : ParallelQuery, IEnumerable<TSource>, IEnumerable
13     {
14         // 摘要:
15         //     返回循環訪問序列的枚舉數。
16         //
17         // 返回結果:
18         //     循環訪問序列的枚舉數。
19         public virtual IEnumerator<TSource> GetEnumerator();
20     }
21 } 
View Code

System.Linq.ParallelEnumerable:

1 // 摘要:
2     //     提供一組用於查詢實現 ParallelQuery{TSource} 的對象的方法。 這是 System.Linq.Enumerable 的並行等效項。
3     public static class ParallelEnumerable {}
View Code

我們在用的時候只需要將它原本的類型轉換成ParallelQuery<TSource>類型就行了;

1 var items = from item in orders.AsParallel() where item.OName.Contains("1") select item;
View Code

Linq 的擴展性真的很方便,可以隨意的封裝任何跟查詢相關的接口;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM