前言
并發、并行,同步、異步、互斥、多執行緒,我太難了,被這些詞搞懵了,前面我們在寫.Net基礎系列的時候寫過了關于.Net的異步編程,那么其他的都是些什么東西呀,今天我們首先就來解決這個問題,把這些詞搞懂搞透,理清邏輯,然后最后我們進入并行編程的介紹,
概念初識
首先我們看并發和并行:
并發:并發指的是在作業系統中,一個是時間段內有多個程式在運行,但是呢,這幾個程式都運行在同一個處理機上,并且任意時間點都是一個程式運行在處理機上,
并行:并行指的是在作業系統中,一個時間段內有多個程式在運行,但是呢,這幾個程式分別運行在不同的處理機上,也就是說這些程式是一起運行的,
簡單理解也就是并發就像三個包子給一個人吃,一口吃一個包子,并行就是三個包子給三個人吃,三個人一口分別吃三個包子,
然后我們看看異步與多執行緒概念:
剛剛我們講到并發的理解概念,其中并發包含兩種關系-同步和互斥,同步和互斥我們都是相對于臨界資源來談的,
互斥:行程間相互排斥使用臨界資源的現象就叫互斥,就好比行程A在訪問List集合的時候,行程B也想訪問,但是A在訪問,B就阻塞等待A訪問完成之后才去訪問,
同步:行程間的關系不是臨界資源的相互排斥,而是相互依賴,例如行程B需要讀取一個集合結果,但是這個集合結果需要行程A回傳,當行程A沒有回傳集合結果時,行程B就會因為沒有獲得資訊而阻塞,當行程A回傳資訊,行程B就可以獲得資訊被喚起繼續運行,
多執行緒:多執行緒可以說是程式設計的一個邏輯概念,多執行緒實作了執行緒的切換,使其看起來似乎是在同時運行多個執行緒一樣,是行程中并發運行的一段代碼,
異步:異步與同步相對應,同步是行程間相互依賴,異步是行程間相互獨立,不需要等待上一個行程的結果,可以做自己的事情,
上面我們就介紹完了并發、并行、互斥、同步、多執行緒、異步,我們總結下其中關聯吧:
異步與多執行緒并不相等,異步是需要達到的目的,多執行緒是一個是實作異步的一種手段,最后達到的目的是什么呢?就是并發中執行緒的切換,同步也可以實作執行緒切換,但是由于同步中IO等待會浪費時間,所以同步切換行程與異步切換進行就有明顯的時間差距,
Parallel
今天我們介紹的是Parallel類,該類位于System.Threading.Tasks命名空間中,依次來實作資料和任務的并行性,
其中定義了并行的for和foreach的靜態方法、還包含著Parallel.Invoke()用于任務的并行性,我們下面就來看看吧,
Parallel.For()
Parallel.For()方法類似于#中的for回圈陳述句,但是Parallel.For()是可以并行運行的,不過并行運行并不保證迭代運行的順序,我們來看看,
public static void ForEx() { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<Test> list = new List<Test>(); for (int i = 0; i < 100; i++) { Test test = new Test(); test.Name = "Name:" + i; test.Index = "Index:" + i; test.Time = DateTime.Now; list.Add(test); Task.Delay(10).Wait(); Console.WriteLine("C#中的for回圈:" + i); } stopwatch.Stop(); Console.WriteLine("for 0-100 執行完成 耗時:{0}", stopwatch.ElapsedMilliseconds); } public static void ParallelFor() { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<Test> lists = new List<Test>(); Parallel.For(1, 100, i => { Test tests = new Test(); tests.Name = "Name:" + i; tests.Index = "Index:" + i; tests.Time = DateTime.Now; lists.Add(tests); Task.Delay(10).Wait(); Console.WriteLine("Parallel中的ParallelFor回圈:" + i); }); stopwatch.Stop(); Console.WriteLine("ParallelFor 0-100 執行完成 耗時:{0}", stopwatch.ElapsedMilliseconds); }
static void Main(string[] args) { Console.WriteLine("Start"); ForEx(); Console.WriteLine("for回圈完成"); ParallelFor(); Console.WriteLine("End"); }

這里我們可以看到最后的運行結果圖使用for回圈的執行下來都是依次執行,按照相應的順序,但是我們使用Parallel.For()的時候運行下來,也輸出了所有的結果,但是其順序就沒有保證了,
Parallel.ForEach()
我們再看Parallel.ForEach()提供了一個并行處理資料的機制,這里類似于foreach陳述句,但是是以一部方式遍歷,這里沒有確定遍歷的順序,其執行順序也就是不保證的,
#region ForEach 陳述句比較 public static void ParallelForEach() { List<Test> result = new List<Test>(); for (int i = 1; i < 100; i++) { Test model = new Test(); model.Name = "Name" + i; model.Index = "Index" + i; model.Time = DateTime.Now; result.Add(model); } Parallel.ForEach<Test>(result, s => { Console.WriteLine(s.Name); }); } #endregion
static void Main(string[] args) { ParallelForEach(); }

我們看這里的運行結果,對資料集合進行了遍歷處理,但是其運行的順序不定,是亂序的結果,這也就是異步遍歷的一個表現,
ParallelLoopState
下面我們來看ParallelLoopState,它提供了兩個方法,一個是Break、一個是Stop,
Break:表示并行回圈執行了當前迭代后應盡快停止執行,篩選出符合條件的執行,可能輸出完全,
Stop:表示并行回圈應盡快停止執行,遇到符合條件后停止并行回圈,可能不完全輸出,
下面我們看看代碼:
public static List<Test> GetListTest() { List<Test> result = new List<Test>(); for (int i = 1; i < 100; i++) { Test model = new Test(); model.Name = i.ToString(); model.Index = "Index" + i; model.Time = DateTime.Now; result.Add(model); } return result; } public static void BraekFor() { var result = GetListTest(); Parallel.For(0, result.Count, (int i, ParallelLoopState ls) => { Task.Delay(10).Wait(); if (i > 50) { Console.WriteLine("Parallel.For使用Break停止當前迭代:" + i); ls.Break(); return; } Console.WriteLine("測驗Parallel.For的Break:" + i); }); } public static void StopFor() { var result = GetListTest(); Parallel.For(0, result.Count, (int i, ParallelLoopState ls) => { Task.Delay(10).Wait(); if (i > 50) { Console.WriteLine("Parallel.For使用Stop停止 迭代:" + i); ls.Stop(); return; } Console.WriteLine("測驗Parallel.For的Stop:" + i); }); }
static void Main(string[] args) { BraekFor(); StopFor(); }


我們看對于Parallel.For()來說這個案例,使用Break()停止當前迭代會輸出符合條件所有結果,但是我們使用Stop的時候輸出部分的時候就停止了,
public static void BraekForEach() { var result = GetListTest(); Parallel.ForEach<Test>(result, (Test s, ParallelLoopState ls) => { Task.Delay(10).Wait(); if (Convert.ToInt32(s.Name) > 50) { Console.WriteLine("Parallel.ForEach使用Break停止當前迭代:" + s.Name); ls.Break(); return; } Console.WriteLine("測驗Parallel.ForEach的Break:" + s.Name); }); } public static void StopForEach() { var result = GetListTest(); Parallel.ForEach<Test>(result, (Test s, ParallelLoopState ls) => { Task.Delay(10).Wait(); if (Convert.ToInt32(s.Name) > 50) { Console.WriteLine("Parallel.ForEach使用Stop停止 迭代:" + s.Name); ls.Stop(); return; } Console.WriteLine("測驗Parallel.ForEach的Stop:" + s.Name); }); }
static void Main(string[] args) { BraekForEach(); StopForEach(); }


我們再對Parallel.ForEach進行測驗,發現對于Stop和Break的用法和意義是一樣的,
Parallel.Invoke()
上面我們介紹了Parallel.For和Parallel.ForEach以及提供的兩個方法Break和Stop,上面介紹的這些都是對資料的并行處理執行,下面我們介紹Parallel.Invoke(),它是針對于任務的并行運行處理,
這里我們需要注意以下幾點:
1、如果我們傳入4個任務并行,那么我們至少需要四個邏輯處理內核(硬體執行緒)才可能使四個任務一起運行,但是當其中一個內核繁忙,那么底層的調度邏輯就可能會延遲某些方法的初始化執行,
2、Parallel.Invoke()所包含的并行任務不能相互依賴,因為運行執行的順序不可保證,
3、使用Parallel.Invoke()我們需要測驗運行結果,觀察邏輯內核使用率以及實作加速,
4、使用Parallel.Invoke()會產生一些額外的開銷,例如分配硬體執行緒,
我們看下面的案例:
下面我們對一個集合的資料進行添加然后輸出,下面我們分為四組測驗,500條資料和1000條資料各兩個,分別是一般的同步任務和Parallel.Invoke()的并行任務執行,再觀察其運行的時間比較,
#region Parallel.Invoke()使用共同資源 public static List<Test> _tests = null; public static void TaskFive_One() { for (int i = 0; i < 500; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskFive_One 500條資料第一個方法 執行完成"); } public static void TaskFive_Two() { for (int i = 500; i < 1000; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskFive_Two 500條資料第二個方法 執行完成"); } public static void TaskFive_Three() { for (int i = 1000; i < 1500; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskFive_Three 500條資料第三個方法 執行完成"); } public static void TaskFive_Four() { for (int i = 1500; i < 2000; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskFive_Four 500條資料第四個方法 執行完成"); } public static void TaskOnethousand_One() { for (int i = 0; i < 1000; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskOnethousand_One 1000條資料第一個方法 執行完成"); } public static void TaskOnethousand_Two() { for (int i = 1000; i < 2000; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskOnethousand_Two 1000條資料第二個方法 執行完成"); } public static void TaskOnethousand_Three() { for (int i = 2000; i < 3000; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskOnethousand_Three 1000條資料第三個方法 執行完成"); } public static void TaskOnethousand_Four() { for (int i = 3000; i < 4000; i++) { Test test = new Test(); test.Name = i.ToString(); test.Index = i.ToString(); test.Time = DateTime.Now; _tests.Add(test); } Console.WriteLine("TaskOnethousand_Three 1000條資料第四個方法 執行完成"); } #endregion
static void Main(string[] args) { //五百條資料順序完成 Stopwatch swFive = new Stopwatch(); swFive.Start(); Thread.Sleep(3000); _tests = new List<Test>(); TaskFive_One(); TaskFive_Two(); TaskFive_Three(); TaskFive_Four(); swFive.Stop(); Console.WriteLine("500條資料 順序編程所耗時間:" + swFive.ElapsedMilliseconds); //五百條資料并行完成 Stopwatch swFiveTask = new Stopwatch(); swFiveTask.Start(); Thread.Sleep(3000); _tests = new List<Test>(); Parallel.Invoke(() => TaskFive_One(), () => TaskFive_Two(), () => TaskFive_Three(), () => TaskFive_Four()); swFiveTask.Stop(); Console.WriteLine("500條資料 并行編程所耗時間:" + swFiveTask.ElapsedMilliseconds); //一千條資料順序完成 Stopwatch swOnethousand = new Stopwatch(); swOnethousand.Start(); Thread.Sleep(3000); _tests = new List<Test>(); TaskOnethousand_One(); TaskOnethousand_Two(); TaskOnethousand_Three(); TaskOnethousand_Four(); swOnethousand.Stop(); Console.WriteLine("1000條資料 順序編程所耗時間:" + swOnethousand.ElapsedMilliseconds); //一千條資料并行完成 Stopwatch swOnethousandTask = new Stopwatch(); swOnethousandTask.Start(); Thread.Sleep(3000); _tests = new List<Test>(); Parallel.Invoke(() => TaskOnethousand_One(), () => TaskOnethousand_Two(), () => TaskOnethousand_Three(), () => TaskOnethousand_Four()); swOnethousandTask.Stop(); Console.WriteLine("1000條資料 并行編程所耗時間:" + swOnethousandTask.ElapsedMilliseconds); }

我們看這次的運行結果,發現我們使用順序編程和并行編程所需要的時間相差無幾的,那么怎么回事呢?我們仔細檢查下,發現我們似乎對資源進行了共享,我們下面處理下,對list集合不進行共享看看,
#region Parallel.Invoke()不使用共同資源 public static void TaskFive_One() { List<Test> tests = new List<Test>(); for (int i = 0; i < 500; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskFive_One 500條資料第一個方法 執行完成"); } public static void TaskFive_Two() { List<Test> tests = new List<Test>(); for (int i = 500; i < 1000; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskFive_Two 500條資料第二個方法 執行完成"); } public static void TaskFive_Three() { List<Test> tests = new List<Test>(); for (int i = 1000; i < 1500; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskFive_Three 500條資料第三個方法 執行完成"); } public static void TaskFive_Four() { List<Test> tests = new List<Test>(); for (int i = 1500; i < 2000; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskFive_Four 500條資料第四個方法 執行完成"); } public static void TaskOnethousand_One() { List<Test> tests = new List<Test>(); for (int i = 0; i < 1000; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskOnethousand_One 1000條資料第一個方法 執行完成"); } public static void TaskOnethousand_Two() { List<Test> tests = new List<Test>(); for (int i = 1000; i < 2000; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskOnethousand_Two 1000條資料第二個方法 執行完成"); } public static void TaskOnethousand_Three() { List<Test> tests = new List<Test>(); for (int i = 2000; i < 3000; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskOnethousand_Three 1000條資料第三個方法 執行完成"); } public static void TaskOnethousand_Four() { List<Test> tests = new List<Test>(); for (int i = 3000; i < 4000; i++) { Test test = new Test(); test.Name = "Name" + i.ToString(); test.Index = "Index" + i.ToString(); test.Time = DateTime.Now; tests.Add(test); } Console.WriteLine("TaskOnethousand_Four 1000條資料第四個方法 執行完成"); } #endregion
static void Main(string[] args) { Stopwatch swTest = new Stopwatch(); swTest.Start(); Thread.Sleep(3000); TaskFive_One(); TaskFive_Two(); TaskFive_Three(); TaskFive_Four(); swTest.Stop(); Console.WriteLine("500條資料 順序編程所耗時間:" + swTest.ElapsedMilliseconds); //五百條資料并行完成 swTest.Restart(); Thread.Sleep(3000); Parallel.Invoke(() => TaskFive_One(), () => TaskFive_Two(), () => TaskFive_Three(), () => TaskFive_Four()); swTest.Stop(); Console.WriteLine("500條資料 并行編程所耗時間:" + swTest.ElapsedMilliseconds); //一千條資料順序完成 swTest.Restart(); Thread.Sleep(3000); TaskOnethousand_One(); TaskOnethousand_Two(); TaskOnethousand_Three(); TaskOnethousand_Four(); swTest.Stop(); Console.WriteLine("1000條資料 順序編程所耗時間:" + swTest.ElapsedMilliseconds); //一千條資料并行完成 swTest.Restart(); Thread.Sleep(3000); Parallel.Invoke(() => TaskOnethousand_One(), () => TaskOnethousand_Two(), () => TaskOnethousand_Three(), () => TaskOnethousand_Four()); swTest.Stop(); Console.WriteLine("1000條資料 并行編程所耗時間:" + swTest.ElapsedMilliseconds); }

我們看下我們修改共享資源后,對于500條資料的運行結果,順序編程比并行編程還是要快點,但是在1000條資料的時候并行編程就明顯比順序編程要快了,而且在測驗中并行編程的運行順序也是不固定的,我們在日常編程中我們需要衡量我們的應用是否需要并行編程,不然可能造成更多的性能損耗,
專案原始碼地址
世界上那些最容易的事情中,拖延時間最不費力,堅韌是成功的一大要素,只要在門上敲得夠久夠大聲,侄訓把人喚醒的,
歡迎大家掃描下方二維碼,和我一起學習更多的知識??

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/110581.html
標籤:C#
上一篇:C# 方法
下一篇:占位符使用(豎式計算)
