《Pattern-Oriented Software Architecture, Patterns for Concurrent and Networked Objects》Vol.2 筆記

GoF的23種經典模式使得設計模式開始成爲程序員的通用語言。《Pattern-Oriented Software Architecture, Patterns for Concurrent and Networked Objects》 Volume 2主要總結了並行系統中四大類16種設計模式。這裏是一個摘錄整理。

Service Access and Configuration

Wrapper Facade

它將底層的系統級API包裝成加面向對象的,統一的和可移植的接口。像Thread, Mutex, Condition Variable, Socket之流的系統層C接口直接調用的話不僅可移植性差且容易出錯。通過高級語言諸如C++可以把一些語義上的規則(如離開scope時釋放鎖,mutex/conition variable不可拷貝等)通過語言特性封裝起來。像Android當中Looper,Thread, Mutex等都可以看作是該模式的應用。

Component Configurator

實現組件的動態加載和配置。主要包含四個部分:Component提供了統一接口,具體的Components提供了特定類型的實現,Component repository管理所有Concrete components,Component configurator使用Component repository來協調具體Components的配置。如Android中的ServiceManager採用了這種模式來管理系統中的Service。

Interceptor

允許應用通過註冊符合預定接口的服務實現擴展Framework的目的。簡單地說就是Framework提供的hook函數接口。首先,系統給出interceptor回調接口,而應用負責實現具體的interceptor。類似於Observer模式,具體Interceptor可以向Dispatcher註冊,之後Dispatcher收到事件的時候,會將之分發到所有註冊的interceptor。

Extension Interface

允許同一個Component暴露多個接口,從而防止Interface爆炸式增長。主要目標是改動Component的功能且不break已有的Client代碼。解決方案是讓Client通過單獨的接口訪問Component,而不是用統一接口。對於同一個對象,每個Client只看到它所需要的接口。比如Android 4.4中的BufferQueue類,既實現了生產者接口又實現了消費者,不同Client各取所需,改動互不影響。

Event Handling Patterns

Reactor(Dispatcher, Notifier)

對於事件驅動的應用,Reactor允許它可以對從多個Client發來的請求進行分路和分發。它可以從一個或多個事件源以同步方式等待事件的到來,並將分路和分發機制和應用的事件處理解耦。缺點是其伸縮性差,因爲所有操作都是單線程串行執行的。如Android中的Looper,還有libev庫,其中的消息循環都是Reactor模式的實現。

Proactor

和Reactor的純被動等待方式不同,Proactor模式中應用組件會通過開始一個或多個異步操作請求來主動發起控制和數據流。而對服務請求的分路和分發是被異步操作結束事件所觸發的。它需要OS對異步I/O的支持,如Windows中的GetQueuedCompletionStatus()。

當Client向Service發起一個異步操作請求,就創建一個ACT。它是completion handler的閉包,其包含了指定completion handler所需要的所有信息。然後會被連同請求一起傳給Service。Service不會修改它,在申請完成時,會傳回給Client。Android中的MessageHandler就是同步ACT變體的例子。

Acceptor-Connector

Acceptor-Connector模式將連接及初始化與之後的服務處理解耦。大體上,兩個工廠Acceptor和Connector用於創建Service handler的連接。當連接建立後,Acceptor和Connector工廠初始化相應的service handler。之後Service handler就可以通過其中的transport handle來交換數據和執行操作了,並不再需要與Acceptor或Connector工廠發生交互。舉例來說,在Android中,Client要與SurfaceFlinger連接的話,要通過ComposerService向SurfaceFlinger請求連接,SurfaceFlinger返回SurfaceComposerClient。然後SurfaceComposerClient就可以和SurfaceFlinger中的Client直接通信了。

Synchronization Patterns

Scoped Locking(Synchronized Block, Resource-Acquisition-is-initialization, Guard, Execute Around Object)

一般的鎖實現的問題之一是它不是exception-safe的。一旦丟出異常或調用函數丟出異常,它會不釋放鎖就返回。結果就是可能會產生死鎖。該模式基於C++中的析構函數,保證當控制流進入scope時獲得鎖,而當離開scope時釋放鎖。Android裏的Mutex::Autolock和C++裏的lock_guard皆屬於此類。

Strategized Locking

通過Strategy模式將同步機制參數化。同步策略可以是mutex, rw/lock或semaphore等。可用多態或是模板參數實現。前者適於運行時才知道策略的,後者適用於編譯時的。

Thread-Safe Interface

Thread-Safe Interface模式的主要目的是防止組件間調用的self-deadlock。具體地,將對象方法分爲接口層和實現層。接口層函數一般是公有的,它們負責獲得釋放鎖和同步檢查,然後調用相應的實現層函數。實現層函數假定調用者已經獲得相應的鎖,而且不會往上調用到接口層的方法(否則會死鎖)。很多系統中函數後面跟Locked或者_l後綴代表是實現層函數。

Double-Checked Locking(Lock Hint)

該模式用於在只需執行一次的臨界區代碼中減少競爭和同步開銷。比如一段只需被執行一次的臨界區需要同步,如果簡單加鎖意味着以後每一次都需要檢查鎖,這是額外的開銷。典型的比如Singleton中的instance初始化。基本思想是在進入臨界區前做兩次檢查,一次在加鎖前,一次在加鎖後。這樣,當第二次控制流來到時,在加鎖前的檢查就跳轉了,減去了鎖的開銷。POSIX中mutex基本已經用futex實現,從而降低無競爭時鎖的開銷。但在性能關鍵的場合,比如kernel中,它還是被廣泛應用的。

Concurrency Patterns

Active Object(Concurrent Object)

Active Object模式將函數執行和函數調用解耦,以簡化不同線程中對於共享狀態的同步。函數的調用在調用者所在線程,而函數的執行在單獨線程,即該線程程爲Active object的Scheduler線程。因此函數調用和執行可以並行進行。Proxy將函數調用轉爲調用請求,放在Scheduler的Activation list中。Servant提供這個Active object的實現。比如在單UI線程的系統中,UI控件是Active object,其它工作線程需要修改時都會請求主線程來執行。

Monitor Object( Thread-safe Passive Object)

Monitor Object保證無論有多少個線程在這個對象上執行,每個時刻在對象中只有一個方法執行。Active Object和Monitor object模式都可以用於調度對象上方法的並行執行。區別在於前者在單獨線程,後者沒有。前者的優點是可以實現一些調度策略。缺點是有上下文切換和數據移動的額外開銷。例如Android裏的很多需要並行訪問的類都有一個Mutex對象,在公有接口調用時都會先對其加鎖。

Half-Sync/Half-ASync

Half-Sync/Half-ASync主要將同步I/O操作和異步I/O操作解耦,並且通過加一個隊列層來調節同步層和異步層之間的通信。具體來說,對於一些耗時操作,以同步方式放到單獨線程中,從而簡化並行模型。對於一些短時操作(Signal handler),以異步方式來調用提高性能。如果它們之間要通信,則通過隊列層來傳遞消息。簡單來說,下層拿到請求後放入隊列,上層拿出處理。 例子比如Android中的AsyncTask。

Leader/Followers

多個線程輪流共享一組事件源,並進行檢測,分路,分發和處理這些事件源上的服務請求。該模式的核心是一個線程池。具體地,每次只有一個線程(Leader)等待一組事件源。其它線程(Followers)排列等待成爲Leader。當檢測到到有事件來時,Leader線程成工作線程,它完成事件的分路和分發。最終調用Event handler。多個處理線程可以並行工作。當處理完後,工作線程又變回Follower線程,等待再一次變成Leader線程。Java中的ScheduledThreadPoolExecutor和DelayQueue均採用此模式。

Thread-Specific Storage

Thread-Specific Storage模式可以讓多個線程訪問“邏輯”上的全局數據,但其物理對象是本地的。這樣就不會引起同步開銷。一般會爲Thread-Specific Storage的訪問寫Proxy來將TLS封裝起來。這個模式的使用就不勝枚舉了,比如錯誤代碼,或者Android中的Looper,都是放在TLS中的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章