摘
要
本論文描述主動對象(Active Object)模式。該模式使方法執行與方法調用去耦合,以簡化對駐留在它自己的線程式控制制中的對象的同步訪問。主動對象模式允許一或多個交錯訪問資料的獨立執行的線程被建模為單個對象。這一併發模式能良好地適用於廣泛的生產者/消費者和讀者/作者應用類。該模式通常用於需要多線程伺服器的分布式系統中。此外,客戶應用,比如視窗系統和網路瀏覽器,採用主動對象來簡化並發和非同步網路操作。
6.1
意圖
主動對象設計模式使方法執行與方法調用去耦合,以增強並發、並簡化對駐留在它自己的線程式控制制中的對象的同步訪問。
6.2
別名
並發對象和Actor。
6.3
例子
為示範主動對象模式,考慮一個通訊網關[1]的設計。網關使協作的組件去耦合,並允許它們進行互動,而無需彼此直接依賴[2]。圖6-1中所示的網關在分布式系統中將來自一或多個供應者進程的訊息路由到一或多個消費者進程[3]。在我們的例子中,網關、供應者和消費者在連線導向的協議TCP[4]之上進行通訊。因此,當網關軟體嘗試向遠地消費者發送資料時,可能會遇到來自TCP傳輸層的流量控制。TCP使用流量控制來確保快速的生產者或網關不會過快地生產出資料,以致慢速消費者或擁擠的網路不能緩衝和處理這些資料。為了改善所有供應者和消費者的端到端服務品質(QoS),整個網關進程不能在任何到消費者的串連上阻塞以等待流量控制緩解。此外,當供應者和消費者的數目增加時,網關還必須能高效地擴充,防止阻塞並提高效能的一種有效方法是在網關設計中引入並發。並發應用允許執行對象O的方法的線程式控制制與調用O的方法的線程式控制制去耦合。而且,在網關中使用並發還使TCP串連被流量控制的線程的阻塞不會阻礙TCP串連未被流量控制的線程的執行。 圖6-1 通訊網關
6.4
上下文 對運行在相互分離的線程式控制制中的對象進行訪問的客戶。
6.5
問題
許多應用受益於使用並發對象來改善它們的QoS,例如,通過允許應用並行地處理多個客戶請求。並發對象駐留在它們自己的線程式控制制中,而不是使用單線程
被動對象棗這些對象在調用其方法的客戶的線程式控制制中執行它們的方法。但是,如果對象並發執行,且這些對象被多個客戶線程共用,我們必須同步對它們的方法和資料的訪問。在存在這樣的問題時,會產生三種壓力:
- 對對象方法的並發調用不應阻塞整個進程,以免降低其他方法的QoS:例如,如果在我們的網關例子中,一個外出的到消費者的TCP串連因為流量控制而阻塞,網關進程仍應該能在等待流量控制緩解的同時,排隊新的訊息。同樣地,如果其他外出的TCP串連沒有被流量控制,它們應該能獨立於任何阻塞串連發送訊息給它們的消費者。
- 對共用對象的同步訪問應該很簡單:如果開發人員必須顯式地使用低級同步機制,比如像擷取和釋放互斥鎖(mutex),常常難於對網關這樣的應用進行編程。一般而言,當對象被多個客戶線程訪問時,需進行同步約束的方法應該被透明地序列化。
- 應用應設計為能透明地利用硬體/軟體平台上可用的並行機制:在我們的網關例子中,發往不同消費者的訊息應該被網關並行地在不同的TCP串連上發送。但是,如果整個網關被編寫為僅在單個線程式控制制中運行,效能瓶頸就不可能通過在多處理器上運行網關而被透明地克服。
6.6
解決方案
對於每個需要並發執行的對象,使對對象方法的請求與方法執行去耦合。這樣的去耦合被設計用於使客戶線程看起來像是調用一個平常的方法。該方法被自動轉換為方法請求對象,並傳遞給另一個線程式控制制,在其中它又被轉換回方法,並在對象實現上被執行。主動對象由以下組件組成:
代理(Proxy)[5, 2]表示對象的介面,
僕人(Servant)提供對象的實現。代理和僕人運行在分離的線程中,以使方法調用和方法執行能並發運行:代理在客戶線程中運行,而僕人在不同的線程中運行。在運行時,代理將客戶的方法調用(Method Invocation)轉換為
方法請求(Method Request),並由
調度者(Scheduler)將其儲存在
啟用隊列(Activation Queue)中。調度者持續地運行在與僕人相同的線程中,當啟用隊列中的方法請求變得可運行時,就將它們出隊,並指派給實現主動對象的僕人。客戶可通過代理返回的“
期貨”(future)擷取方法執行的結果。
6.7
結構
主動對象模式的結構在下面的Booch類圖中示範: 在主動對象模式中有六個關鍵的參與者:
代理(Proxy
)
- 代理提供一個介面,允許客戶使用標準的強型別程式語言特性,而不是線上程間傳遞鬆散類型的訊息,來調用主動對象的可公用訪問的方法。當客戶調用代理定義的方法時,就會在調度者的啟用隊列上觸發方法請求對象的構造和排隊;所有這些都發生在客戶的線程式控制制中。
方法請求(Method Request
)
- 方法請求用於將代理上的特定方法調用的上下文資訊,比如方法參數和代碼,從代理傳遞給運行在分離線程中的調度者。抽象方法請求類為執行主動對象方法定義介面。該介面還包含守衛(guard)方法,可用於確定何時方法請求的同步約束已被滿足。對於代理提供的每個主動對象方法(它們在其僕人中需要同步的訪問),抽象方法請求類被子類化,以建立具體的方法請求類。這些類的執行個體在其方法被調用時由代理建立,並包含了執行這些方法調用和返回任何結果給客戶所需的特定的上下文資訊。
啟用隊列(Activation Queue
)
- 啟用隊列維護一個有界緩衝區,內有代理建立的待處理的方法請求。該隊列跟蹤哪些方法請求將要執行。它還使客戶線程與僕人線程去耦合,以使兩個線程能並發運行。
調度者(Scheduler
)
- 調度者運行在與其客戶不同的線程中,它管理待處理的方法請求的啟用隊列。調度者決定下一個出隊的方法請求,並在實現該方法的僕人上執行。這樣的調度決策基於各種標準,比如像方法被插入到啟用隊列中的順序;以及同步約束,例如特定屬性的滿足或特定事件的發生,比如在有界資料結構中有新的條目空間變得可用。調度者通常使用方法請求守衛來對同步約束進行求值。
僕人(Servant
)
- 僕人定義被建模為主動對象的行為和狀態。它實現在代理中定義的方法及相應的方法請求。僕人方法在調度者執行其相應的方法請求時被調用;因而,僕人在調度者的線程式控制制中執行。僕人還可提供其他方法,由方法請求用於實現它們的守衛。
期貨(Future
)
- 期貨[7, 8]允許客戶在僕人結束方法的執行後擷取方法調用的結果。當客戶通過代理調用方法時,期貨被立即返回給客戶。期貨為被調用的方法保留空間,以儲存它的結果。當客戶想要擷取這些結果時,它可以阻塞或者輪詢,直到結果被求值和儲存到期貨中,然後與期貨“會合”。
6.8
動力
特性 示範主動對象模式中的協作的三個階段:
- 方法請求構造和調度:在此階段,客戶調用代理上的方法,從而觸發方法請求的建立;方法請求維護方法的參數綁定,以及其他任何執行方法和返回結果所需的綁定。代理隨後將方法請求傳遞給調度者,後者將其放入啟用隊列中。如果方法被定義為“兩路”(two way)[6]的,一個期貨的綁定被返回給調用該方法的客戶。如果方法被定義為“單路”(oneway)的,就沒有期貨被返回,也就是,它沒有傳回值。
- 方法執行:在此階段,調度者在與其客戶不同的線程中持續運行。在此線程中,調度者監控啟用隊列,並確定哪些方法請求已成為可啟動並執行,例如,當它們的同步約束已被滿足時。當方法請求成為可啟動並執行,調度者就使其出隊,綁定到僕人,並指派僕人上的適當方法。當此方法被調用時,它可以訪問/更新它的僕人的狀態並建立它的結果。
- 完成:在最後的階段中,結果(如果有的話)被儲存在期貨中,而調度者持續地監控啟用隊列中,看是否有可啟動並執行方法請求。在一個兩路方法完成後,客戶可以通過與期貨會合來擷取它的結果。一般而言,任何與期貨會合的客戶都可以擷取它的結果。當方法請求和期貨不再被引用時,它們就被刪除或被記憶體回收。
6.9 實現 這一部分解釋使用主動對象模式構建並發應用所涉及的步驟。使用主動對象模式實現的應用是6.3網關的一部分。圖6-2示範該例子的結構和參與者。這一部分中的例子使用ACE構架[9]的可複用組件。ACE提供了一組豐富的可複用C++封裝和構架組件,可跨越廣泛的OS平台執行常見的通訊軟體任務。
- 實現僕人:僕人定義被建模為主動對象的行為和狀態。客戶可通過代理來訪問僕人所實現的方法。此外,僕人還可包含其他方法,方法請求可以用這些方法來實現守衛,以允許調度者對運行時同步約束進行求值。這些約束決定調度者指派方法請求的順序。
在我們的網關例子中,僕人是一個訊息佇列,緩衝待處理的遞送給消費者的訊息。對於每一個遠地消費者,都有一個Consumer Handler(消費者處理器),其中含有一個到消費者進程的TCP串連。此外,Consumer Handler含有一個被建模為主動對象的訊息佇列,並通過MQ_Servant來實現。當從供應者傳遞到網關的訊息在等待被發送到它們的遠地消費者時,每個Consumer Handler的主動對象訊息佇列就儲存這些訊息。下面的類提供了這個僕人的介面: class MQ_Servant{public:MQ_Servant (size_t mq_size); // Message queue implementation operations.void put_i (const Message &msg);Message get_i (void); // Predicates.bool empty_i (void) const;bool full_i (void) const; private:// Internal Queue representation, e.g., a// circular array or a linked list, etc.}; 圖6-2 將消費者處理器的訊息佇列實現為主動對象 put_i和get_i方法分別實現隊列的插入和移除操作。此外,僕人還定義了兩個斷言(Predicate):empty_i和full_i,可區分三種內部狀態(1)空,(2)滿,以及(3)既不為空白也不為滿。這些斷言用於方法請求的看守方法的實現,後者允許調度者強制實施運行時同步約束;這些同步約束規定僕人的put_i和get_i的調用順序。注意MQ_Servant類是怎樣設計,以使同步機制始終外在於僕人。例如,在我們的網關例子中,MQ_Servant類中的方法並不包括任何實現同步的代碼。該類僅僅提供實現僕人功能和檢查它的內部狀態的方法。這樣的設計避免了“
繼承異常”[10, 11, 12, 13]問題;如果子類需要不同的同步策略,該問題將會妨礙僕人實現的複用。因而,對主動對象的同步約束的改變不需要影響它的僕人實現。
毛毛的小窩