.NET4.0並行計算技術基礎——來自bitfan(數字世界一凡人)

 

摘記:

一、任務的取消

      ParallelLoopState.Break()方法,:在完成當前的這輪工作之後,不再執行後繼的工作,但在當前這輪工作開始之前“已經在執行”的工作,則必須完成。
      ParallelLoopState.Stop方法時,不但不會再創建新的線程執行並行循環,而且當前“已經在執行”的工作也應該被中止
 

      Stop Break 的方法的區別非常微妙,需要仔細體會,可以簡單地用兩句話來表達:

n  ParallelLoopState.Stop 方法中止“當前”及“以後”的工作任務,會導致 ParallelLoopState 對象的 IsStop 屬性值等於 true

n  ParallelLoopState.Break() 方法僅中止“以後”的工作任務,會導致 ParallelLoopState 對象的 LowestBreakIteration 屬性值等於 true

細則:

1.

2.
 

3.定義一個CancellationTokenSource,調用cts.Cancel()請求終止並行計算任務。但是必須進行設置聲明

並且,在執行方法中,被取消任務後,再執行任務時,必須

 二、摘錄

   1..依據“阿姆達爾定律”得出的這個結論令人沮喪!但由此也可以得到一個重要的結論:如果希望使用並行計算來提升程序的性能,那麼應儘可能地減少程序中串行代碼的比例

 

   2.並行計算帶來的複雜性
         上面所介紹的例子非常清晰地展示出並行程序設計的特殊性,並不是“並行”總比“串行”快的,到底怎樣才能獲得最大的並行加速係數,需要仔細地設計並行算法,並且應該在多個典型的軟硬件環境中進行對比測試,最終才能得到理想的並行設計方案。
         開發並行程序的關鍵在於要找到一個合適的任務分解方案,並行總要付出一定的代價,比如線程同步、線程通訊、同步緩衝數據等都是開發並行程序必須認真考慮的問題。

 

3.從性能比較的結果來看,並行計算並非總具有性能優勢,這也提醒我們要注意並行計算的應用場合 

         1每個數據項要執行的處理工作量很大,需要耗費較多的時間

         2)要處理的數據集合很大

 

 

 

4.Parallel.For的工作原理:

       TPL在Parallel.For方法內部創建了一個任務對象rootTask,然後調用此對象的RunSynchronously()方法以“同步”方式執行並行循環,注意,別被這裏的單詞“Synchronously(中文譯爲“同步地”)給欺騙了,此方法絕不是串行執行的。因爲此方法接收一個參數,此參數引用一個任務調度器對象,由此調度器對象將任務進行分解,交由線程池中的線程執行,這是實現並行循環的關鍵!

       任務交給線程池中的線程執行之後,Parallel.For方法調用rootTask .Wait()方法等待所有線程完成工作。最後,銷燬rootTask對象。

通過仔細分析源碼,我們明白了爲何在串行代碼中使用Parallel.For會出現“串行à並行à串行”這種執行順序。

另外,我們還可以得到另一個結論:

使用Parallel啓動的並行計算,在底層使用Task來完成。

 

5.Parallel的三個靜態方法:

 

 

6.任務並行庫的工作原理
         任務由線程負責執行,爲了獲取較高的性能,TPL使用線程池中的線程,並且使用了一個與線程池直接集成的“任務調度器(Task Scheduler)”來負責分派工作任務給線程,這個調度器使用的任務分派策略稱爲“Work-stealing”。
   
 
         如圖 19‑16所示,線程池中的每個線程都擁有一個專有的(本地的)任務隊列,當線程創建任務(即Task類的實例)時,默認設置下,這些任務被放入了線程本地工作隊列中。
         如果任務本身是通過調用ThreadPool.QueueUserWorkItem()添加的,則此任務會被添加到一個全局隊列(global queue)中,這一全局隊列就是圖 19‑16中所示的“線程池任務隊列”。
         以下是任務調度器實現任務調度的基本過程:
       當任務調度器開始分派任務時,它先檢查一下創建此任務的線程是不是線程池中的線程(這種線程擁有一個本地的任務隊列),如果不是,此任務被加入到線程池全局任務隊列中,如果是,任務調度器檢查此任務是否設置了TaskCreationOptions.PreferFairness標記,如果設置了,則此任務被加入到線程池全局任務隊列中,否則,還是被放入到線程的本地隊列中。
       當一個線程開始執行時,它優先搜索自己的專有任務隊列,當此隊列爲空時,它纔會去搜索全局任務隊列。由此可見,這種調度策略實際上是其於優先級的,本地工作隊列比全局隊列擁有更高的優先級。
         上述這種默認的調度策略適用於絕大多數情況,但不可能是所有的情況,如果需要對線程本地隊列和線程池全局隊列中的任務一視同仁,在不改變調度策略的情況下(這個策略是由.NET爲線程池所提供的默認調度器實現的,不可改),可以通過將需要“一視同仁”的Task任務直接放到線程池全局隊列而不是線程本地隊列中實現,其具體的實現方法就是在創建任務時,設置它的 TaskCreationOptions.PreferFairness標記。
 
      提示:
       如果並行執行是通過Parallel類的Invoke、For和ForEach方法啓動的,則不能爲其指定TaskCreationOptions.PreferFairness標記,只有在顯式創建Task類的代碼中可以設置此標記。下一小節將介紹如何直接使用Task類進行基於“任務”的並行編程。
 
         下面對任務並行庫的工作原理作一個小結。
         簡單地說:
線程就是“工人”,它負責執行“任務”,任務由任務調度器負責分配。
         任務調度器具有很強的智能性,它能自動協調各個任務的分配,不讓“忙”的線程“忙死”,“閒”的線程“閒死”。從線程的角度看,由於有任務調度器的公平管理,所有線程都是“團結互助”的“雷鋒”。
         將線程之間合作的工作從線程自身的職責中“剝離”出來,交由任務調度器來統一協調管理,這是.NET 4.0並行計算任務庫設計的一個關鍵點。如果讓線程自身來負責處理工作任務的合理分配,必然會在線程函數內增加同步的代碼,這會讓整個軟件系統變得複雜和難於調試。
         我們可以適當地將TPL的這種設計思想引申到社會生活領域:如果將線程比喻爲“政府官員”,那麼,任務調度器就可以看成是一種“制度”,正是在“制度”的制約之下,“官員”纔可能廉潔公正。
         在現實社會中,指望貪官他們“良心”發現而自己“金盆洗手”是不現實的,必須建立起一種有效的制度,讓所有官員都置於強有力的監督之下,“貪污”的行爲自然會受到極大的制約。這是題外話了。

 

7.瞭解任務Task的狀態
         “風蕭蕭兮易水寒,壯士一去兮不復還”,與線程對象一樣,每一個Task對象都會經歷一個生命週期,在這個生命週期的每個特定階段,對象處於一個特定的狀態,並且不可能由後一個狀態“迴轉”到前一個狀態。簡單地說,Task對象的生命是一條單行線,一旦上路,就只能往前走,直到生命的終結,期間絕無走回頭路的可能。

 

 

如圖 19‑17所示,Task對象擁有8個狀態,這些狀態之間可以相互轉換。
         其中,Created是起始狀態,而Canceled、Faulted和RanToCompletion是3個終止狀態,其餘狀態都是中間狀態。
         通過對Task類特定的方法的調用,Task對象會自動進行狀態的轉換。通常情況下軟件工程師無需考慮這一轉換過程,因爲它們是由TPL基礎架構直接管理的。
         Task類提供了一個Status屬性來表明當前對象所處的狀態,但出於使用方便考慮,Task類另外還提供了3個相關屬性用於確定對象是否處理3個終止狀態之一:IsCanceled、IsFaulted和IsCompleted。

 

8.Task類的一些使用

 

 9.Task<TResult> 類派生自 Task 類.實現得到任務處理結果  

 

 

10.處理並行任務中引發的異常:

 當某個Task對象引發了一個未被捕獲的異常時,TPL會將此異常包裝到一個特殊的AggregateException異常對象中。

         AggregateException類的InnerExceptions屬性包容了此輪並行代碼中引發的所有異常。

一個典型的並行程序異常處理代碼框架如下:

情況比較複雜的是,任務是可以嵌套的。比如一個並行任務可能會創建多個子任務,而這些子任務又會創建更多的“孫子任務”,由此構成一個任務對象的樹型結構。

         當這棵“任務對象樹”中的任何一個節點(即任務對象自身)引發了一個未捕獲的異常時,TPL都會爲此任務對象創建一個AggregateException對象,把前述那個未捕獲的異常對象添加到創建好的AggregateException對象的InnerExceptions集合中,然後,再把這一個AggregateException對象添加到其父任務對象所關聯的AggregateException對象的InnerExceptions集合中(這段話比較拗口,請讀者仔細閱讀)。

         這樣一來,任務對象的嵌套就導致了AggregateException對象的嵌套,而這種嵌套還是遞歸進行的,這就給編寫異常處理代碼帶來了麻煩,你必須“下鑽”到AggregateException對象樹的最底層才能得到真正的異常對象,以下是訪問兩層“異常樹”的示例代碼:

     爲了解決需要“遞歸”編寫異常處理的問題,AggregateException類提供了一個將多層的AggregateException對象樹“展平”爲一層的方法——Flatten()。使用此方法時,異常處理代碼不需要遍歷AggregateException對象樹:

   經過“展平”之後,AggregateException. InnerExceptions將只包容具體的異常對象,不再包容嵌套的AggregateException對象。

11.屏蔽掉特定的異常:

AggregateException類提供了一個Handle()方法來實現:直接處理或忽略掉某種特定種類的異常,這時,肯定不需要將這些異常對象加入到AggregateException對象中。

 

 12.PLINQ

默認情況下,PLINQ查詢要處理的數據被認爲“順序無關緊要”.
AsParallel()或AsParallel<T>():將LINQ查詢轉爲並行執行
ParallelEnumerable類的擴展方法AsSequential():強制將其轉爲串行模式
AsOrdered()和AsSequential()是不一樣的.
AsSequential()強制PLINQ查詢以串行方式執行
AsOrdered()仍是並行執行的,只不過並行執行的結果先被緩存起來,然後再按原始數據順序進行排序,纔得到最後的結果。
很明顯,給PLINQ查詢加上AsOrdered()子句將會影響到程序的性能,因此,儘量避免使用它。
在一些情況下,可以通過修改PLINQ查詢的順序避免使用AsOrdered()子句

 

 

 

正因爲並行程序開發、測試和調試都比串行程序要困難,所以一般都是編寫程序的串行版本,等其工作正常之後將其升級替換爲並行版本
三.批註:
“.NET4.0並行計算技術基礎(12)”“迎接新一輪的技術進步浪潮”摘記,內容查看原帖更易理解。重要內容包括:ParallelEnumerable類、ParallelQuery類、中途取消PLINQ操作、設置工作線程數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章