準則: 並行
這個準則協助開發人員選擇最佳方式來滿足軟體系統內的並行需求。
關係
主要說明

簡介

良好的設計,巧妙之處在於選擇「最佳」方式來符合一組需求。良好的並行系統的設計,巧妙之處則往往在於選擇最簡單的方式來滿足並行需求。例如,避免重複發明輪子,便應該是設計人員各項首要規則之一。好的設計型樣和設計慣例早已發展備妥,它們可以解決大部分問題。面對並行系統的複雜度,您只應使用業經證明的解決方案,儘可能簡化設計。

並行方法

完全在一部電腦內進行的並行作業,稱為執行緒。如同所有並行作業,執行緒在發生之後,仍是一個抽象概念。我們實際擷取執行緒的最佳方式,是呈現它在特定時間點的狀態。

利用電腦來呈現並行作業,最直接的方式是每項作業都有一部專用的電腦。不過,這個成本通常太高,不一定有助於解決衝突。因此,通常是利用多工的形式,在相同實體處理器上支援多項作業。在這個情況下,處理器及相關資源(如記憶體和匯流排)是共用的。(不幸,這項資源共用也可能會帶來原始問題中所不存在的新衝突。)

最常見的多工形式是提供「虛擬」處理器給每一項作業。這個虛擬處理器通常稱為流程或作業。一般而言,每個流程都有自己的位址空間,在邏輯上,這個空間有別於其他虛擬處理器的位址空間。這可以保護流程免於因意外覆寫彼此的記憶體而造成衝突。不幸,在流程之間切換實體處理器時所需要的額外負荷,通常成本過高。它涉及 CPU 內大量的暫存器集交換(環境定義切換),甚至最新的高速處理器也可能需要花數百微秒。

為了減少這個額外負荷,許多作業系統都提供了在單一流程內併入多個輕量型執行緒的功能。流程內的執行緒會共用該流程的位址空間。這可以減少環境定義切換所涉及的額外負荷,但會增加記憶體衝突的可能。

對某些通訊量高的應用程式而言,即使是輕量型的執行緒切換,額外負荷也可能是高到無法接受。在這類狀況中,通常是利用應用程式的某些特性來實現更加輕量的多工形式。

系統的並行需求可能會大幅影響系統的架構。決定將單流程架構的功能移到多流程的架構,也會使系統結構的許多維度發生重大變更。這時可能需要引進其他機制(如遠端程序呼叫),進而實質改變系統的架構。

系統可用性需求以及管理其他流程和執行緒的額外負荷,都需要考量。

如同大部分的架構決策,變更流程架構會將一組問題有效地換成另一組問題:

方法

優點

缺點

單流程,無執行緒
  • 簡單
  • 快速進行流程內的傳訊
  • 難以平衡工作量
  • 無法調整成多處理器
單流程,多執行緒
  • 快速的流程內部訊息
  • 不含跨流程通訊的多工作業
  • 多工較好,不含「重量級」流程的額外負荷
  • 應用程式必須是「執行緒安全」
  • 作業系統必須具備有效的執行緒管理功能
  • 必須考量共用記憶體問題
多流程
  • 當增加處理器時,可以妥善調整
  • 比較容易分散於各個節點
  • 對流程界限敏感;跨流程通訊太多會損害效能
  • 交換和環境定義切換成本很高
  • 較難設計

一般發展路徑是從單流程架構開始,加入必須同時進行之行為群組的流程。在這些較寬的分組內,請考慮其他並行需求,在流程內增加執行緒來提高並行程度。

最初的起點是利用為特定目的而建立的主動物件排程器,將許多主動物件指派給單一作業系統作業或執行緒;在這個方法之下,通常有可能獲致超輕量型的並行模擬,只不過它是用單一作業系統作業或單一執行緒,但不可能使用有多個 CPU 的機器。 關鍵決策是將會造成暫停執行的行為隔離在個別執行緒中,使這個造成暫停執行的行為不會成為瓶頸。結果會將含有造成暫停執行的行為之主動物件分隔到它們自己的作業系統執行緒中。

在即時系統中,這個推理也同樣適用於封裝體,每個封裝體都有一個邏輯控制執行緒,可能與其他封裝體共用或不共用作業系統執行緒、作業或流程。

問題

很不幸地,這裡如同許多架構決策一樣,並沒有簡單的回答;正確的解決方案需要謹慎而平衡的方法。您可以利用小的架構原型來探索一組特定選項的連帶作用。在建立流程架構的原型時,請將焦點放在調整流程的數目,最多只到系統的理論上限。請考量下列問題:

  • 流程數目能夠調整到上限嗎? 超出上限之後,還可以將系統推到多遠? 允許潛在成長嗎?
  • 將某些流程改成在共用的流程位址空間中操作的輕量型執行緒,會帶來什麼影響?
  • 當流程數目增加,跨流程通訊 (IPC) 增加,回應時間會出現什麼情況?會有明顯的退化嗎?
  • 組合或重組流程可以減少 IPC 的數量嗎? 這類變更會造成難以平衡負載的大型整體流程嗎?
  • 可以利用共用記憶體來減少 IPC 嗎?
  • 當配置時間資源時,所有流程都應該取得「相等的時間」嗎? 時間配置可攜嗎? 變更排程優先順序有潛在的缺陷嗎?

跨物件通訊

主動物件可以彼此進行同步或非同步的通訊。同步通訊很有用,因為它可以利用嚴格控制的排序來簡化複雜的協同作業。也就是說,當主動物件在操作執行至完成的步驟,而這個步驟涉及其他主動物件的同步呼叫時,在整個序列完成之前,可以忽略其他物件所起始的任何並行互動。

在某些情況下,這非常有用,但它也可能造成問題,因為有時更重要的高優先順序事件卻必須等待(優先順序相反)。這個情況會惡化,因為同步呼叫的物件本身有可能暫停執行,以等待對它自己之同步呼叫的回應。這可能造成無限制的優先順序倒轉。在最極端的情況下,如果同步呼叫鏈形成圓圈,便能造成死鎖。

非同步呼叫能夠具備有限的回應時間,可以避免這個問題。不過,依軟體架構而定,非同步通訊通常會導致比較複雜的程式碼,因為主動物件可能需要隨時回應多個非同步事件(每個事件都可能需要與其他主動物件的更複雜的非同步互動)。這可能很難實作,且容易造成錯誤。 

使用含有確定訊息交付的非同步傳訊技術,可以簡化應用程式設計作業。即使無法獲得網路連線或遠端應用程式,應用程式也能夠繼續作業。非同步傳訊並不阻止在同步模式中使用它。每當可以使用應用程式時,同步技術都會需要可用的連線。由於已知連線存在,所以處理確定流程會比較容易進行。

在 Rational Unified Process 所建議的即時系統方法中, 封裝體會根據特定 通訊協定,利用信號來進行非同步的通訊。不過,仍有可能利用信號組,每個方向一個信號,來實現同步通訊。

實用

雖然主動物件切換環境定義的額外負荷可能很低,但有些應用程式仍可能覺得這個成本無法接受。當需要高效處理大量資料時,便會發生這個情況。在這些情況中,我們必須回頭使用被動物件和更傳統(但風險較高)的並行管理技術,例如號誌。

不過,這些考量不表示我們必須完全放棄主動物件的方式。 即使在這類資料密集應用程式中,對效能敏感的部分,仍是整體系統中較小的部分。表示系統的其餘部分仍可以使用主動物件參照範例。

一般而言,就系統設計來說,效能只是設計準則之一。如果系統很複雜,其他準則,如可維護性、是否容易變更、好不好理解等,即使不是比較重要,至少也同等重要。主動物件的方式有一個明顯的好處,因為它隱藏了並行作業和並行管理的許多複雜面向,且相對於低階的技術專用機制,它可讓設計以應用程式專用的詞彙來表達。

啟發

將焦點放在並行元件之間的互動

並行元件若是不互動,便幾乎是不值得討論的問題。設計所面臨的所有挑戰,幾乎都必須處理並行作業之間的互動,因此,我們必須先將精力集中在瞭解互動。以下是我們要問的部分問題:

  • 互動是單向、雙向或多向?
  • 有用戶端伺服器關係或主從關係嗎?
  • 需要某種形式的同步化嗎?

瞭解互動之後,我們便可以思考它的實作方式。您應該選取實作來產生與系統效能目標一致的最簡單的設計。在回應外部產生的事件時,一般而言,效能要求包括整體通訊量和可接受的延遲時間。

對即時系統而言,這些問題尤其關鍵,它通常比較無法容忍效能的變化,如回應時間「跳動不定」,或錯過截止時間。

隔離和封裝外部介面

在應用程式中處處內含關於外部介面的特定假設是不當的作法,而且讓多個控制執行緒暫停執行來等待某個事件,效率會很差。請反過來,將專用的偵測事件作業指派給單一物件。當事件發生時,這個物件可以通知任何需要知道這個事件的物件。這項設計的基礎是廣為人知且已獲證明的設計型樣「觀察者」型樣 [GAM94]。如果要更靈活,它很容易延伸到「發佈者-訂閱者型樣」,在這個型樣中,發佈者物件是扮演事件偵測者和對事件感興趣的物件(「訂閱者」)之間的媒介 [BUS96]。

隔離和封裝暫停執行和輪詢行為

系統中的動作有可能是因為出現外部產生的事件而觸發。一個非常重要的外部產生事件,有可能只是如時鐘滴答聲所代表的時間本身的推移。其他外部事件則來自連接到外部硬體的輸入裝置,其中包括使用者介面裝置、流程感應器,以及通往其他系統的通訊鏈結。對於與外界密切連接著的即時系統而言,這是絕不容懷疑的真實。

為了使軟體偵測到事件,它必須暫停執行等待岔斷,或定期檢查硬體來瞭解是否發生了事件。在後一種情況中,週期性的循環可能必須短一點,以免失去存在期間短的事件或多重發生的情況,或只是將事件的發生和偵測之間的延遲縮到最小。

這方面的有趣之處在於,無論事件多麼罕見,有些軟體就是必須暫停執行來等待它,或頻繁地檢查它。但系統必須處理的許多(倘若不是大部分的話)事件都是很罕見的;在任何給定的系統中,大部分時間都不會發生什麼重要的事情。

電梯系統提供許多這方面的好例子。在電梯的生命中,重要事件包括要求服務、乘客選擇樓層、乘客用手攔住門,以及從一個樓層移到另一個樓層。部分這些事件要求在關鍵時間回應,但對照所需回應時間的時間尺度,它們是極少的。

單一事件可以觸發許多動作,這些動作可能會隨著各種物件的狀態而不同。另外,系統的不同配置也可能用不同的方式來使用相同的事件。例如,當電梯通過樓層時,就應該更新電梯室的顯示畫面,電梯本身必須知道它在哪裡,才會知道如何回應新的呼叫和旅客選擇的樓層。不一定每個樓層都會顯示電梯位置。

反應行為比輪詢行為好

輪詢的成本很高,它需要系統的某一部分定期停止進行中的工作來檢查是否發生了某個事件。如果事件必須及時回應,系統檢查事件到達的頻率便必須很高,會進一步限制能夠完成的其他工作量。

配置岔斷事件,由岔斷來啟動依於事件的程式碼,則遠遠有效得多。雖然人們有時會覺得岔斷「成本很高」,避免使用它,但恰當使用岔斷會比重複輪詢有效得多。

偏好以岔斷為事件通知機制的情況是事件的到達既少又隨機的情況,在這個情況下,大部分輪詢工作都會發現事件尚未發生。偏好輪詢的情況是事件以可預期的方式定期到達的情況,在這個情況下,大部分輪詢工作都會發現事件已發生。在這中間有一個點,對於輪詢或反應行為沒有偏好,這時兩者的效果一樣好,選擇哪一項無關緊要。不過,在大部分情況下,儘管真實世界的事件是隨機的,最好仍是採用反應行為。

事件通知比資料廣播好

廣播資料(通常是使用信號)的成本很高,通常很浪費 - 可能只有少數物件對資料感興趣,但每個物件(或許多物件)都必須停止工作來檢查這個資料。利用通知,只通知對發生了某事件感興趣的物件,是比較好、比較不耗資源的方法。將廣播限於許多物件都需要注意的事件(通常是計時或同步化事件)。

多用輕量型機制,少用重量型機制

更明確地說:

  • 使用被動物件和同步方法呼叫,這時不會有並行問題,但會有即時回應的問題。
  • 針對絕大部分應用程式層次的並行概念,使用主動物件和非同步訊息。
  • 利用 OS 執行緒來隔離造成暫停執行的元素。主動物件可以對映到 OS 執行緒。
  • 在最大隔離程度上,使用 OS 流程。如果是必須獨立啟動和關閉的程式,或可能需要分散的子系統,便需要個別的流程。
  • 利用個別 CPU 來進行實體分散或用來作為原始馬力。

也許開發有效並行應用程式最重要的準則是儘可能使用最輕量型的並行機制。在支援並行功能時,硬體和作業系統軟體都扮演了主要的角色,但它們兩者都提供相當重量級的機制,應用程式設計者會有大量工作。在可用的工具和並行應用程式的需求之間有一道鴻溝,我們必須架起橋樑。

主動物件有助於利用兩個主要特性來形成這個間隙的橋樑:

  • 它們會將可利用 OS 或 CPU 提供的任何基礎機制來實作的基本並行單位(控制執行緒)封裝起來,以將設計的抽象統一起來。
  • 當主動物件共用單一 OS 執行緒時,它們會變成非常有效率的輕量型並行機制,在其他情況下,必須在應用程式中直接實作它們。

主動物件也會形成程式語言所提供之被動物件的理想環境。完全從並行物件的基礎來設計系統,不含程式或流程之類的程序化構件,往往會產生更模組化、更有內聚力,且更容易理解的設計。

避免偏執效能

在大部分系統中,不到 10% 的程式碼使用了 90% 的 CPU 循環。

許多系統設計者的作法都彷彿是每一行程式碼都必須最佳化。請不要這樣,請將您的時間花在將程式碼中最常執行或花最多時間的 10% 最佳化。其他 90% 的設計要強調可理解性、可維護性、模組化,以及容易實作。

選擇機制

非功能需求和系統架構會影響用來實作遠端程序呼叫的機制選項。以下是各種選擇方案取捨方式的概觀。 

機制 使用 備註
傳訊 非同步存取企業伺服器 傳訊中介軟體可以處理佇列作業、逾時及回復/重新啟動狀況來簡化應用程式設計作業。您也可以在虛擬同步模式中使用傳訊中介軟體。 傳訊技術通常可以支援大的訊息。部分 RPC 方法可能會有訊息大小的限制,需要其他處理大型訊息的程式設計。
JDBC/ODBC 資料庫呼叫 這些都是與資料庫無關的介面,Java Servlet 或應用程式用它們來呼叫在相同或不同伺服器中的資料庫。
原生介面 資料庫呼叫 許多資料庫供應商都實作了它們自己的資料庫的原生應用程式介面,效能比 ODBC 好,但犧牲了應用程式可攜性。
遠端程序呼叫 呼叫在遠端伺服器的程式 如果您有應用程式建置器會處理 RPC 層次的程式設計,您可能不需要在 RPC 層次上設計程式。
交談式 在電子商業應用程式中很少使用 通常是利用 APPC 或 Socket 之類通訊協定來進行的低階程式對程式通訊。

摘要

許多系統都需要並行行為和分散式元件。在這些問題上,大部分程式語言所提供的協助都不多。我們已知道,在好的抽象之下,我們才能瞭解應用程式中的並行需求以及在軟體中實作它的選項。另外,我們也已知道,雖然並行軟體先天上就比非並行軟體複雜,但弔詭的是,並行軟體卻能夠大幅簡化必須處理現實世界並行現象的系統設計。