淺談並行編程中的任務分解模式

來源:互聯網
上載者:User

 本文觀點引自英特爾公司系統架構師Shameem Akhter和進階軟體工程師Jason Roberts的文章

轉載自:http://software.intel.com/zh-cn/articles/discussion-on-parrallel-programming-decomposition/?cid=sw:prccsdn604

      並行編程使用線程來使得多個操作能夠同時運行。並行編程主要包括應用程式中線程設計,開發和部署以及線程間相互協調和各自的操作。

      在下文中我們將討論怎樣分割適合線程化大小的編程任務來多任務化一個應用程式。

設計線程

      不熟悉並行編程的開發人員通常對例如物件導向的傳統的編程模式感到非常適應。在傳統的編程模式下,程式以預先定義的起點開始運行,譬如main函數,然後接連地做完一系列任務。如果程式依賴使用者互動,主要的工作代碼通常被封裝在一個處理使用者事件的迴圈裡。

      從一個約定事件開始,譬如點擊按鈕,程式運行一段已經制定的順序行為,最終以等待使用者下個動作結束。

      當設計這樣的程式時,程式員喜歡一個相對簡單的編程模式因為在任意一段給定的時間內只有一個事件發生。如果程式任務必須按某種方式順序運行, 程式員必須在這些事件上特意安排順序。在這個過程的任一時刻,程式運行一步接著下一步,最終基於預先確定的參數到達一個預見的結果。

      從這種線性模式到並行編程模式,程式設計者必須重新思考程式的過程流。相比順序執行序列限制,程式員應該識別出那些能被並行化的行為。

      要這樣做,他們必須把程式看作一組相互間有依賴關係的任務。把程式分解成一些獨立的任務並識別這些任務間的依賴性。這個過程被稱為分解。

      一個問題有許多分解方式: 按任務,資料,或者按資料流。下面的表總結了這些分解的形式。您將很快看到,這些不同分解形式對應了不同的編程行為。

分解類型

設計

評論

任務

不同的行為分配給不同的線程

通常在GUI應用

資料

多個線程對不同的資料集執行同樣的操作

通常在音頻處理,做圖和科學編程

資料流

一個線程的輸出是第二個線程的輸入

需要特別關注消除開始和結束的延遲

主要的分解形式總結

任務分解

      按功能分解一個程式被稱為任務分解。這是一種實現並存執行最簡單的方式。使用這種方法,每個任務被按目錄分類。

      如果它們中的兩個能同時運行,它們會被程式員按此調度。以這種方式並行運行任務通常需要對每個函數做小量修改來避免衝突,並指出這些任務已經不再連續。

      以園藝工作舉例,任務分解會建議園丁按工作本身的屬性分配任務:如果兩個園丁到達一個客戶家,一個修剪草坪,另一個剷除雜草。修剪草坪和剷除雜草是兩個被分開的功能。

      要完成這兩個功能,園丁們需要確保他們之間相互協調,這樣剷除雜草的園丁就不會坐在待修剪草坪的中間。

      舉個編程的例子,一個任務分解的典型案例是文文書處理軟體,譬如微軟的Word。當使用者開啟一個很長的文檔的時候,他(她)能夠馬上開始輸入文字。當使用者輸入文字時,文檔分頁在後台發生,於是他(她)能夠很容易地看到狀態列中頁數的增加。

      文檔輸入和分頁是兩個獨立的任務,程式員按功能將它們分開來並行運行。如果程式員沒有這樣設計,使用者將不得不在能夠輸入任何文字之前等待整個文檔被分頁。有些朋友可能回憶起這種現象在早期的個人電腦文文書處理器中非常常見。

資料分解

      資料分解,也被認為是資料級並行,將任務按它們處理的資料進行分解,而不是按照任務本身的性質。使用資料分解的程式通常有許多線程在執行同樣的任務,只是處理的資料項目不同。

      舉個例子,假設在一個大的電子資料工作表裡計算值。相比一個線程執行所有的計算,資料分解會建議有兩個線程,每個執行一半的計算量,或者n個線程執行1/n的工作量。

      如果園丁應用資料分解來分解他們的任務,他們兩個會同時修剪一半的草坪,然後兩個人分別剷除一半的雜草。在計算領域,決定哪一種分解形式更高效取決於系統的限制。

      舉例說,如果需要修剪草坪的地塊非常小以至於沒必要有兩個人來修剪草坪,修剪草坪這個任務最好只被分配給一個園丁做,那麼任務分解在這一步是最好的選擇。資料分解或許適用於其它的工作順序,譬如當修剪草坪完成後,兩個園丁並行地來剷除雜草。

      隨著處理器核心數目的增加,資料分解使得任務處理規模增加。這允許在同樣的時間內做更多的工作。

      舉園丁的例子,假設有另外兩個園丁加入了工作。相比分配所有四個園丁到一個地塊工作,我們不如分配兩個新的園丁去另一個地塊,非常有效地增加我們總的任務處理量。

      假設兩位新園丁和兩位老園丁能夠做同樣多的工作,兩個地塊的大小也是一樣的,我們已經在同樣的時間內把工作量翻倍了。

資料流分解

      許多時候,當分解一個問題時,關鍵不是任務應該做什麼事情,而是資料在不同任務中怎樣傳遞。在這些情況下,資料流分解將問題按資料在任務中傳遞的方式來分解。

      生產者/消費者問題是資料流影響程式並存執行能力的著名例子。這裡,一個生產者任務的輸出,成為另一個消費者的輸入。兩個任務被不同的線程執行,直到生產者完成他的部分工作,消費者不能開始工作。

      依然引用園丁的例子,一個園丁準備工具,譬如他承擔為割草機加油,清掃剪刀等類似的任務來提供這些工具給另兩個園丁使用。直到這個準備步驟基本結束,其他園丁的園藝工作才能開始。

      由第一個任務引起的延遲為第二個任務產生一個暫停,在此之後兩個任務才能並行運行。在電腦領域這樣的模式經常發生。

     在常見的編程任務裡,生產者/消費者問題在多個典型的情境發生。譬如,必須對讀文檔做回應的程式就符合這種情境:檔案的輸入/輸出結果成為下一步可能被線程化的工作的輸入。下一步直到讀完或讀到其他處理需要的足夠資訊才會開始執行。另一個編程的例子是分析:一個輸入檔案必須在後端操作之前被分析或者語義分析,譬如編譯器的代碼產生。

生產者/消費者問題有許多需要注意的方面:

1) 如果這種模式沒有正確地執行,消費者和生產者間產生的依賴性會引起重大的延遲。一個效能敏感的設計需要充分理解依賴關係的性質以減小延遲的影響。這也是為了避免消費者線程空閑等待生產者線程的情況。

2) 在完美的情況下,生產者和消費者間的傳遞是完全"清潔"的,就像在檔案分析器的例子中一樣。輸出是上下文相關的,消費者不需要知道生產者的任何事情。然而在很多時候,生產者和消費者並不享受如此乾淨的任務分割,安排他們間的互動需要非常仔細的計劃。

3) 如果當生產者完全做好後消費者開始加工,那麼當其他線程忙著工作時一個線程就保持空閑。這個問題破壞了一個平行處理的重要目標,那就是負載平衡以使得所有能用的線程保持忙碌。由於線程間的邏輯關係,要保持線程平等地被佔用非常困難。

      下面,我們看看流水線模式,它允許開發人員以可升級的模式解決生產者/消費者問題。

不同分解的含義

      不同的分解具有不同的優勢。如果目標是使編程簡便,任務能夠清楚地按功能分割,那麼任務分解通常更合適。

      資料分解增加了一些額外的代碼級複雜度,因此他專供資料非常容易分解而效能又非常重要的情況使用。

      線程化程式的最常見原因就是效能。在這種情況下,分解方式的選擇就更加困難。在許多情況下,選擇依賴於問題的領域:一些任務明顯更適合於其中一種分解方式。

      但是一些任務沒有明顯的偏向。譬如視頻流中的影像處理。在幀與幀間沒有依賴的格式,你需要作出分解模式的選擇。他們應該選擇任務分解麼,那樣一個線程解碼,另一個線程配色,諸如此類?或者選擇資料分解,每個線程做一幀上的所有工作,然後一起開始處理新的一幀?

      回到園丁的例子:如果兩個園丁需要修整兩塊草坪和剷除兩塊花園的雜草,他們應該怎樣工作?是應該一個園丁只修剪草坪,也就是他們選擇基於任務的分解呢?還是兩個人應該同時修剪草坪然後同時剷除雜草?

      在一些情況下,答案很快顯現。例如當一個資源限制存在,譬如只有一個割草機。其它情況下每一個園丁都有一個割草機,答案只能通過仔細分析行為要素來得出。在園丁的例子中,任務分解看起來更好,因為如果只有一個割草機在使用,開始的割草時間就被節省了。

      最終,你通過仔細的計劃和測試來為你的應用程式使用並行編程作出正確的選擇。 根據經驗估計在你決定並行程式設計時比標準的單線程編程中扮演更加重要的角色。

你將面對的挑戰

      使用線程通過允許兩個或多個行為同時發生使得你顯著改進效能。然而,開發人員不能不認識到線程增加了複雜性度量,需要細緻的考慮來正確引導。

      這種複雜度源於程式中多於一個行為發生的自身性質。管理同步行為和他們可能的互動使你面對下面四種問題:

1) 同步是兩個或多個線程協調他們行為的過程。譬如,一個線程在繼續運行前等待另一個完成任務。

2) 互動代表與線程間交換資料相關的頻寬和延遲問題。

3) 負載平衡表示任務在多個線程間的分配,因此他們都處理基本同樣的工作量。

4) 可擴充性是當軟體在更先進的系統上運行時高效利用許多線程的挑戰。譬如,如果一個程式被編寫來充分利用四核處理器,當它在一個八核處理器上運行時它是否能適當地擴充?

      上述每個問題都必須被仔細地處理來最大化程式的效能。

並行編程模式

      對於多年編寫物件導向程式的程式員,他們使用設計模式來邏輯地設計他們的應用程式。並行編程和物件導向編程沒有什麼不同,並行編程問題通常對應於許多知名的編程模式之一。

      下面的表格顯示了一些常見的並行編程模式和他們對應的上述分解類型。

模式

分解

任務級並行

任務

分離並戰勝

任務/資料

幾何分解

資料

流水線

資料流

波陣面

資料流

常見的並行編程模式

      在這部分,我們將提供每種模式及其適用的問題種類的簡要概述,包括:

1) 任務級並行編程模式。在許多情況下,完成並存執行的最佳方法是直接關注任務本身。在這種情況下,任務級並行模式最合適。這種模式下,問題被分解成一組獨立操作的任務。

通常移除任務間的依賴性或使用複製來分離依賴性是必要的。適合這種模式的問題包括線程間沒有依賴性,或線程間的依賴性可從每一個線程中移除。

2) 分而治之模式。在分而治之模式中,問題被分成許多並行的子問題。每個子問題被單獨解決。當每個子問題被解決時,結果合計到最後的結果中。因為每個子問題都能被單獨解決,這些子問題有可能被並存執行。

分而治之的方法被廣泛應用於諸如合并分類的演算法。這些演算法非常容易被並行化。這種模式很好地處理了負載平衡,這點對緩衝的有效利用非常重要。

3) 幾何分解模式。幾何分解模式基於正在解決問題的資料結構的並行化。在幾何分解中,每個線程負責操作資料塊。這種模式可能適用於諸如熱流和聲波傳播之類的問題。

4) 流水線模式。流水線模式的意思類似於一條工廠的產品裝配線。這種尋找並發的方式使計算被分割成一系列階段,每個線程在不同的階段同時工作。

5) 波陣面模式。波陣面模式在處理二維網格中延對角線的資料元素時非常有用。如所示。


波陣面資料模式

      中的數字解釋了資料元素被處理的順序。譬如對角線的元素包括數字3是獨立於之前被處理的數字元素1和2的。

      中陰影中的資料元素表示資料已經被處理。在這種模式下,減小每個線程的空閑時間非常關鍵。負載平衡在此種模式中非常關鍵。

      更加詳細的並行編程設計模式的介紹請參考"並行編程模式"(Mattson 2004)
一書。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.