設計模式- 主動對象(Active Object)

譯者注:
1.    對象分爲主動對象和被動對象,主動對象內部包含一個線程,可以自動完成動作或改變狀態,而一般的被動對象只能通過被其他對象調用纔有所作爲。在多線程程序中,經常把一個線程封裝到主動對象裏面。
2.    在翻譯過程中,發現的原文不妥處被更正。
3.    原文中許多內容一再重複,頗有蛇足之感,取精用宏,刪繁就簡。
4.    嘗試更高程度的意譯。
關鍵詞
這個文章介紹主動對象模式,主動對象是內部擁有自己的控制線程的對象。爲了簡化異步調用的複雜性,這個模式分離了方法的執行和調用。使用這個模式,一個對象中無論是否有獨立的線程,客戶從外部訪問它時,感覺是一樣的。生產者/消費者,讀者/寫者,這兩個應用廣泛的模型中,這種策略非常適合。這個模式常用在多線程的,分佈式系統中。另外,一些客戶端應用,如:視窗系統和網絡瀏覽器,也可以使用主動對象模式,對並行的、異步調起的IO操作簡化處理。

1    目的

主動對象模式隔離了方法執行和方法調用的過程,提高了並行性,對內部擁有控制線程的主動對象,降低了異步訪問的複雜性。

2    別名

並行行爲對象(Concurrent Object and Actor)

3    例子

爲了說明主動對象模式,考慮一個通信網關的設計。網關隔離互相協作的多個組成單元,讓交互過程不直接依賴於對方。參照圖1,一個分佈式系統中,來自多個生產者(衛星設備)的消息,被網管轉發給多個消費者(局域網內的主機)。

 
我們的例子中,生產者和消費者使用TCP協議通信,這是面向連接的通信協議。網關上的進程向消費者發送數據時,操作會發生阻塞。這是因爲,網絡傳輸能力有限,TCP進行流量控制,防止過量的數據不能及時緩衝和處理。

要提高整體的效率,網關處理進程不能因一個連接上的阻塞而等待。另外,當生產者和消費者的數目增加的時候,整個進程必須相應的增加處理能力(譯者注:通過增加處理線程)。

一個提高性能的有效方法,就是使用並行。應用並行後,服務對象擁有獨立線程,線程實際完成操作,和方法調用的過程分開。並且,不同線程處理不同TCP連接,一個連接上的線程被阻塞,不會影響到其他連接的線程。

4    場景

對象的訪問者(Client,下統稱爲客戶)和對象的實現在不同的線程中。

5    問題

許多應用場景,服務者並行處理多客戶端的請求,提高服務的質量(QoS)。被動對象在客戶線程中完成操作過程,主動對象使用專用的線程完成。一個對象的數據被多個線程共享時,必須處理好線程的同步。這導致三個約束。

1.    對一個對象調用不能阻塞整個進程,不能影響其他線程的執行:比如,通信網關的例子中,一個TCP連接上的數據傳送被阻塞,整個進程仍然能繼續處理。同樣,其他沒有被阻塞的網絡連接,應該正常的發送數據。
2.    對共享對象同步訪問的邏輯應該簡化:客戶在使用共享對象地時候,如果面對底層的同步機制,必須記得先要獲取互斥鎖,後要釋放互斥鎖,編碼就比較麻煩。一般情況,共享對象的方法隱藏這些細節,當多個客戶線程訪問同一個對象時,這些約束是透明的。
3.    存在可以平行進行的操作,設計成同時執行:在通信網關的例子中,同時使用多個TCP連接給不同的消費者發送數據。如果網關的進程在單個線程上執行,及時使用多個處理器不能明顯的提高性能。

6    方案

對每一個要求併發執行的對象,分離其方法的調用和執行。這樣,這個對象的客戶就像是調用一個常規的方法一樣。這個方法,自動把任務交給另外的線程完成執行。

主動對象的組成:一個代理者(Proxy)實現外部的訪問接口;一個執行者(Servant)。代理和執行者在不同的線程執行,分離方法調用和執行的過程:代理者在客戶線程中被調用執行,執行者在另外的線程完成操作。運行時,代理者把客戶的調用信息封裝“調用請求”(Method Request),通過調度者(Scheduler)把這個請求放到一個活動隊列(Activation Queue)。調度者和執行者運行在另外的線程中,這個線程啓動後,不斷地從活動隊列中得到“調用請求”對象,派發給執行者完成客戶請求的操作。客戶調用代理者後馬上得到一個預約容器(Future),今後可以通過這個預約容器得到返回的結果。

7    結構

下面使用Booch風格的類圖,對主動對象的組成結構進行說明。(譯者注:這是Booch在《面向對象的分析和設計》書中使用的類圖風格)

在這個模式中,共有六個參與者。
    代理者(Proxy)
    代理者定義了被客戶調用的接口。這個接口是函數調用的方式,而不是像傳統線程通信,使用數據傳遞的方式。當函數被調用的,代理者構造一個“調用請求”對象,並把它放到活動隊列中,這一切都發生在客戶線程中。

    調用請求(Method Request)
    “調用請求”用來保存相關函數調用的部上下文信息,比如函數標識,函數的參數,這些信息將在不同線程間傳遞。一個抽象的“調用請求”類,定義了執行活動對象方法的接口Call。並且包含一個Guard函數,Guard用來檢查調用的條件是否滿足。對代理者提供的每一個主動對象方法,在訪問其執行者的時候需要條件判斷。代理者被調用的時候會創建具體“調用請求”對象,對象中包含了執行這個方法必須的參數和數據的返回方式。

    活動隊列(Activation Queue)
    這個隊列維護了一個緩衝區(譯者注:不一定是先進先出的隊列),緩衝區中存放了待執行的“調用請求”。正是這個隊列分離可客戶線程和執行操作的線程。

    調度者(Scheduler)
    調度者管理活動隊列。調度者決定隊列中的哪一個調用請求先被執行。調度的決定可能決定與多個規則:關鍵字順序,入隊的順序,要滿足的執行條件或等待發生的事件,如:在一個數據結構中出現空閒區。調度者使用Guard方法的調用,來檢查是否滿足執行條件。

    執行者(Servant)
    真正完成操作的對象。執行者實際完成代理者定義的主動對象方法,響應一個“調用請求”。調度者給“調用請求”分派一共執行者,然後調用“調用請求”中的Call方法。對應的執行者的方法將被調用。執行者的方法運行在調度者的線程中。執行者可能同時提供了一些方法供“調用請求”實現Guard。

    預約容器(Future)
    當執行者完成操作,客戶通過預約容器獲取返回結果。當客戶調用代理者的方法後,一共空預約容器馬上返回。預約容器指向一塊內存空間,用來保存返回結果。客戶可以通過輪訓或阻塞調用的方法,通過預約容器得到返回結果。

8    運行

下面的圖說明了一個調用過程中的三個階段。
注×:原圖中“enqueue(M1)”的位置有誤,入隊操作應該在返回Future之前,本圖已經更正。黃色表現客戶線程空間,綠色表示調度者線程空間。
 
1.    構造“調用請求”:在這個階段,客戶調用代理者的一個方法m1()。“調用請求”對象被創建,這個對象中包含了所有的參數。代理者把這個“調用請求”對象傳遞給調度者,調度者把它入隊到活動隊列。如果方法m1()有返回值,就返回一個預約容器(Future),否則不返回。
2.    調度執行:調度者的執行線程中,監控活動隊列,當隊列中的一個請求滿足執行條件時,調度者把它出隊,把一個執行者綁定到這個請求上。然後通過“調用請求”的Call方法,Call再調用執行者的m1(),完成客戶請求的操作。
3.    完成:在這個階段,如果有返回值,就把返回值存儲到對應的預約容器中。然後調度者線程繼續在活動隊列中查找下一個要執行的“調用請求”。客戶就可以在預約容器中找到返回值。當“調用請求”和“預約容器”不在使用的時候,注意銷燬,防止內存漏洞。

9    實現

這一節說明一個主動對象模式使用的過程。這個應用是上面例子的部分實現。圖2說明了各個組成部分。這節的例子,使用到了ACE框架的可重用組件。ACE提供了豐富的包裝器和框架組件,用來完成軟件間的通信任務,並且ACE是跨平臺的。

 
1.    執行者的實現:我們的例子中,執行者是個消息隊列,用來緩衝發送到消費者的消息。對每一個遠程的消費者,對應一個Consumer Handler,Handler中包含一個到消費者進程的TCP連接(譯者注:每一個主動對象要包裝一個Consumer Handler)。每一個Consumer Handler對應的活動對象緩存的消息(譯者:通過活動隊列緩存“請求調用“的方式緩存消息),是從生產者發給網關的,並且等待網關把它發送給對應的消費者。下面的類定義了執行者的接口。
class MQ_Servant
{
public:
  MQ_Servant (size_t mq_size);
  // 消息隊列實現的操作
  void put_i (const Message &msg);
  Message get_i (void);
  // 狀態檢查
  bool empty_i (void) const;
  bool full_i (void) const;
private:
  // 內部隊列的實現,可能是循環數組,鏈表之類
};

put_i和get_i實現隊列的插入和刪除操作。另外的兩個函數,empty_i和full_i用來檢查隊列的狀態,隊列共有三種狀態,(1)空,(2)滿,和(3)非空非滿。這兩個函數將幫助實現“調用請求”的Guard()。
注意,執行者MQ_Servant把線程同步的任務交給了外部。在我們的例子中,MQ_Servant沒有包含任何線程同步的代碼。這個類僅提供了檢查其內部狀態的方法。這種設計避免了“Inheritance anomaly”(繼承反常)問題:如果規定了同步實現,會制約MQ_Servant被重用。而這樣,同步方式的改變不影響MQ_Servant的實現(譯者:放下即自在)。

2.    代理者和“調用請求”的實現:例子中,代理者MQ_Proxy提供了和執行者MQ_Servant一樣的接口函數。另外,代理MQ_Proxy又是一個創造“調用請求”對象的工廠。下面是它的C++代碼。
class MQ_Proxy
{
public:
  //  消息隊列的長度
  enum { MAX_SIZE = 100 };
  MQ_Proxy (size_t size = MAX_SIZE)
    : scheduler_ (new MQ_Scheduler (size)),
  servant_ (new MQ_Servant (size)) {}
  // 調度<put> 在活動對象上執行
  void put (const Message &m) {
    Method_Request *method_request =
    new Put (servant_, m);
    scheduler_->enqueue (method_request);
  }
  // <Get>返回預約容器:Message_Future
  Message_Future get (void) {
    Message_Future result;
    Method_Request *method_request =
      new Get (servant_, result);
    scheduler_->enqueue (method_request);
    return result;
  }
  // ... empty() and full() 用來檢查隊列狀態
protected:
  // 實際完成操作的執行者
  MQ_Servant *servant_;
  // 調度者
  MQ_Scheduler *scheduler_;
};
虛擬基類Method_Request,定義了“調用請求”的接口:
class Method_Request
{
public:
// 檢查是否準備好
virtual bool guard (void) const = 0;
// 執行操作
virtual void call (void) = 0;
};

不同的請求,使用不同的子類定義
class Put : public Method_Request
{
public:
  Put (MQ_Servant *rep,
    Message arg)
    : servant_ (rep), arg_ (arg) {}
  virtual bool guard (void) const {
    // 約束檢查
    return !servant_->full_i ();
  }
  virtual void call (void) {
    // 插入消息
    servant_->put_i (arg_); 
  }
private:
  MQ_Servant *servant_;
  Message arg_;
};

上面的Guard函數,使用了MQ_Servant的full_i函數實現。
另外一個“調用請求”子類:
class Get : public Method_Request
{
public:
  Get (MQ_Servant *rep,
    const Message_Future &f)
    : servant_ (rep), result_ (f) {}
  bool guard (void) const {
    // Synchronization constraint:
    // cannot call a <get_i> method until
    // the queue is not empty.
    return !servant_->empty_i ();
  }
  virtual void call (void) {
    // Bind the dequeued message to the
    // future result object.
    result_ = servant_->get_i ();
  }
private:
  MQ_Servant *servant_;
  // Message_Future result value.
  Message_Future result_;
};

這個對象要使用預約容器,處理最終返回的結構。其內部保存了預約容器。

3.    活動隊列的實現:每一個“調用請求”。一個典型的實現,是一個線程安全的緩衝區。一般還要實現遍歷其元素的循環子(Iterator)。下面是本例的C++實現。
class Activation_Queue
{
public:
  // Block for an "infinite" amount of time
  // waiting for <enqueue> and <dequeue> methods
  // to complete.
  const int INFINITE = -1;
  // Define a "trait".
  typedef Activation_Queue_Iterator
  iterator;
  // Constructor creates the queue with the
  // specified high water mark that determines
  // its capacity.
  Activation_Queue (size_t high_water_mark);
  // Insert <method_request> into the queue, waiting
  // up to <msec_timeout> amount of time for space
  // to become available in the queue.
  void enqueue (Method_Request *method_request,
  long msec_timeout = INFINITE);
  // Remove <method_request> from the queue, waiting
  // up to <msec_timeout> amount of time for a
  // <method_request> to appear in the queue.
  void dequeue (Method_Request *method_request,
    long msec_timeout = INFINITE);
private:
  // Synchronization mechanisms, e.g., condition
  // variables and mutexes, and the queue
  // implementation, e.g., an array or a linked
  // list, go here.
  // ...
};

入隊和出隊的操作,是經典的“生產者、消費者”模型。很容易實現互斥訪問。

4.    調度者的實現:調度者要一般實現一個入隊操作。調度執行線程函數。一般作爲靜態函數存在,調用dispatch,實現調度執行線程。
class MQ_Scheduler
{
public:
  // Initialize the Activation_Queue to have the
  // specified capacity and make the Scheduler
  // run in its own thread of control.
  MQ_Scheduler (size_t high_water_mark);
  // ... Other constructors/destructors, etc.,
  // Insert the Method Request into
  // the Activation_Queue. This method
  // runs in the thread of its client, i.e.,
  // in the Proxy’s thread.
  void enqueue (Method_Request *method_request) {
    act_queue_->enqueue (method_request);
  }
  // Dispatch the Method Requests on their Servant
  // in the Scheduler’s thread.
  virtual void dispatch (void);
protected:
  // Queue of pending Method_Requests.
  Activation_Queue *act_queue_;
  // Entry point into the new thread.
  static void *svc_run (void *arg);
};


例子中,線程的啓動和活動隊列的創建都在調度者的構造函數裏面:
MQ_Scheduler (size_t high_water_mark)
  : act_queue_ (new Activation_Queue
  (high_water_mark))
{
  // Spawn a separate thread to dispatch
  // method requests.
  Thread_Manager::instance ()->spawn (svc_run,
  this);
}

線程函數非常簡單,就是調用Dispatch:
void *
MQ_Scheduler::svc_run (void *args)
{
  MQ_Scheduler *this_obj =
    reinterpret_cast<MQ_Scheduler *> (args);
  this_obj->dispatch ();
}

Dispatch的實現如下:
virtual void
MQ_Scheduler::dispatch (void)
{
  // Iterate continuously in a
  // separate thread.
  for (;;) {
    Activation_Queue::iterator i;
    // The iterator’s <begin> call blocks
    // when the <Activation_Queue> is empty.
    for (i = act_queue_->begin ();
    i != act_queue_->end ();
    i++) {
      // Select a Method Request ‘mr’
      // whose guard evaluates to true.
      Method_Request *mr = *i;
      if (mr->guard ()) {
        // Remove <mr> from the queue first
        // in case <call> throws an exception.
        act_queue_->dequeue (mr);
        mr->call ();
        delete mr;
      }
    }
  }
}

5.    異步調用以後,客戶對返回結果的檢測。調用活動對象的客戶,如何獲取和處理返回值?這有不同的策略。有下面三種返回值策略。
1)    同步調用,阻塞等待。客戶線程阻塞,一直到操作完成、數據返回。
2)    同步調用,限時等待。客戶線程阻塞,一直到數據返回、數據返回或者發生超時。
3)    異步調用。預約容器對象,提供某種異步方式返回數據或執行失敗信息。
預約容器種的空間,被多個線程共享,當所有的線程都不再使用的時候,才能被清空內存。要特別注意。
在我們的例子種,Message_Future如下定義:
class Message_Future
{
public:
  // Copy constructor binds <this> and <f> to the
  // same <Message_Future_Rep>, which is created if
  // necessary.
  Message_Future (const Message_Future &f);aa
  // Constructor that initializes <Message_Future> to
  // point to <Message> <m> immediately.
  Message_Future (const Message &m);
  // Assignment operator that binds <this> and <f>
  // to the same <Message_Future_Rep>, which is
  // created if necessary.
  void operator= (const Message_Future &f);
  // ... other constructors/destructors, etc.,
  // Type conversion, which blocks
  // waiting to obtain the result of the
  // asynchronous method invocation.
  operator Message ();
};

可以使用引用計數的方法,處理Message的清除。

客戶通過預約容器獲取數據的兩種方式:
立即方式:
MQ_Proxy mq;
// ...
// Conversion of Message_Future from the
// get() method into a Message causes the
// thread to block until a message is
// available.
Message msg = mq.get ();
// Transmit message to the consumer.
send (msg);


延遲方式:
// Obtain a future (does not block the client).
Message_Future future = mq.get ();
// Do something else here...
// Evaluate future in the conversion operator;
// may block if the result is not available yet.
Message msg = Message (future);

10    完成的例子

通信網關程序內部,包含生產者(Supplier)和消費者(Consumer)的Handler,它們分別是遠程生產者和遠程消費者的代理。如下面的圖3所示,生產者的Handler從遠程設備上接收消息,分析消息中的地址,根據消息中的地址查找路由表,確定哪一個遠程的消費者應該接收這個消息。路由表維護地址到消費者Handler的影射關係。每個消費者的Handler實際通過對應的TCP連接把數據送出。
 
每一個消費者Handler使用上面講的主動對象的模式,內部包含一個消息隊列(譯者:通過保存“調用請求”的活動隊列間接實現了消息的緩存)。來實現消息的異步發送。
class Consumer_Handler
{
public:
  Consumer_Handler (void);
  // Put the message into the queue.
  void put (const Message &msg) {
    message_queue_.put (msg);
  }
private:
  // Proxy to the Active Object.
  MQ_Proxy message_queue_;
  // Connection to the remote consumer.
  SOCK_Stream connection_;
  // Entry point into the new thread.
  static void *svc_run (void *arg);
};


生產者的Handler,使用下面的方式,給消費者發送消息。
Supplier_Handler::route_message (const Message &msg)
{
  // Locate the appropriate consumer based on the
  // address information in the Message.
  Consumer_Handler *ch =
    routing_table_.find (msg.address ());
  // Put the Message into the Consumer Handler’s queue.
  ch->put (msg);
};

消費者的Handler,就是Cosumer_Handler,在構造函數裏面創建其調度處理線程。
Consumer_Handler::Consumer_Handler (void)
{
  // Spawn a separate thread to get messages
  // from the message queue and send them to
  // the consumer.
  Thread_Manager::instance ()->spawn (svc_run,
  this);
}

下面是消費者Handler的線程函數的實現
void *
Consumer_Handler::svc_run (void *args)
{
  Consumer_Handler *this_obj =
  reinterpret_cast<Consumer_Handler *> (args);
  for (;;) {
    // Conversion of Message_Future from the
    // get() method into a Message causes the
    // thread to block until a message is
    // available.
    Message msg = this_obj->message_queue_.get ();
    // Transmit message to the consumer.
    this_obj->connection_.send (msg);
  }
}

當一個消費者的網絡傳送被阻塞的時候,只會阻塞其對應的線程,不會影響到其它消費者的Handler的處理。

11    變化

集成的調度者:
在實現主動對象模式的時候,爲了減少對象的個數。可以把代理者和執行者的角色都分派到調度者身上。甚至“調用請求”的Call函數也可以由調度者實現。比如下面的代碼,消息隊列例子的集成實現方式。
class MQ_Scheduler
{
public:
  MQ_Scheduler (size_t size)
    : act_queue_ (new Activation_Queue (size))
  {}
  // ... other constructors/destructors, etc.,
  void put (const Message &msg) {
    Method_Request *method_request =
    // The <MQ_Scheduler> is the servant.
    new Put (this, msg);
    act_queue_->enqueue (method_request);
  }
  Message_Future get (void) {
    Message_Future result;
    Method_Request *method_request =
    // The <MQ_Scheduler> is the servant.
    new Get (this, result);
    act_queue_->enqueue (method_request);
    return result; 
  }
  // ...
private:
  // Message queue servant operations.
  void put_i (const Message &msg);
  Message get_i (void);
  // Predicates.
  bool empty_i (void) const;
  bool full_i (void) const;
  Activation_Queue *act_queue_;
  // ...
};


這樣集成後,減少了組件,實現更加簡化。當然,這樣也帶來了缺點,調度者必須知道代理者和執行者的具體類型,具體實現。這樣就很難在不同的活動對象中,重用調度者。

消息的直接傳遞:
更近一步的簡化,代理者和執行者都刪除掉。在客戶線程和調度者線程之間直接使用數據的方式傳遞消息。
c
lass Scheduler
{
public:
  Scheduler (size_t size)
    : act_queue_ (new Activation_Queue (size))
  {}
  // ... other constructors/destructors, etc.,
  // Enqueue a Message Request in the thread of
  // the client.
  void enqueue (Message_Request *message_request) {
    act_queue_->enqueue (message_request);
  }
  // Dispatch Message Requests in the thread of
  // the Scheduler.
  virtual void dispatch (void) {
    Message_Request *mr;
    // Block waiting for next request to arrive.
    while (act_queue_->dequeue (mr)) {
      // Process the message request <mr>. 
    }
  }
  protected:
  Activation_Queue *act_queue_;
  // ...
};

因爲沒有了代理者,客戶直接創建“調用請求”對象,然後調用調度者的函數把它入隊到活動隊列。同樣的,沒有了執行者,調度者的線程,在活動隊列中得到請求,直接執行完成。

一般來說,這樣這樣實現的一個消息傳遞的機制,比實現一個主動對象要簡單的多。消息傳遞這種複雜的邏輯直接暴露給其客戶,不但增加開發的難度,還容易滋生BUG,這樣想來,不如把這種邏輯封裝在主動對象的內部。具體如何選擇,根據實際情況和自己的喜好而定。

預約容器的泛型實現:
一個泛型的預約容器可以使用返回值的類型進行定製。預約容器實現了一個一次寫多次讀的同步機制。當容器中的值還沒有準備好的時候,客戶的訪問操作被阻塞。這個泛型預約容器,部分實現了讀者/寫者模型,又部分實現了生產者/消費者模型。
下面是C++模板實現的例子
template <class T>
class Future
{
  // This class implements a ‘single write, multiple
  // read’ pattern that can be used to return results
  // from asynchronous method invocations.
public:
  // Constructor.
  Future (void);
  // Copy constructor that binds <this> and <r> to
  // the same <Future> representation
  Future (const Future<T> &r);
  // Destructor.
  ~Future (void);
  // Assignment operator that binds <this> and
  // <r> to the same <Future>.
  void operator = (const Future<T> &r);
  // Cancel a <Future>. Put the future into its
  // initial state. Returns 0 on success and -1
  // on failure.
  int cancel (void);
  // Type conversion, which obtains the result
  // of the asynchronous method invocation.
  // Will block forever until the result is
  // obtained.
  operator T ();
  // Check if the result is available.
  int ready (void);
private:
  Future_Rep<T> *future_rep_;
  // Future representation implemented using
  // the Counted Pointer idiom.
};

這個模板可以如下使用:
/
/ Obtain a future (does not block the client).
Future<Message> future = mq.get ();
// Do something else here...
// Evaluate future in the conversion operator;
// may block if the result is not available yet.
Message msg = Message (future);


分佈式活動對象:
代理者和調度者之間跨過網絡。代理者把要把“調用請求”對象序列化,然後通過網絡傳輸給另外機器上的調度者,調度者接收並再造“調用請求”對象。

使用線程池:
使用線程池,可以讓一個活動對象支持多個執行者。多個執行者提供相同的服務。每一個執行者運行在不同的線程中,由調度者統一調度,當有新的請求時,調度者馬上安排一個工作線程工作。


12    已知的應用

1.    CORBA ORBS
2.    ACE Framework
3.    Siemens MedCom
4.    Siemens Call Center management system:
5.    Actors
譯者:此節只列出他們的名字,感興趣的同志請參考原文。和看《設計模式》的時候情況一樣,我不太關注這一節。

13    後果

有下面的好處:
1.    增強了程序的並行性,降低了同步的複雜性。客戶線程和異步調起操作並行執行。同步的複雜性由調度者獨立處理。
2.    讓多個耗時的操作並行執行。只要軟件和硬件支持,可以讓多個活動的對象彼此不干擾地同時運行。
3.    方法的執行和調用的順序可以不一致。方法的調用是異步調用。而方法的執行決定於如何調度。

當然,主動對象也有以下負面的影響
1.    性能過多消耗:系統消耗的程度決定於調度者的實現。用戶態和系統態的上下文切換,同步信號的時候,數據的傳送都會帶來消耗。一般說來,主動對象模式適合大粒度的對象,對很小的對象使用這個模式容易帶來性能的過度消耗。請和其它併發模式比較如監控者模式。
2.    增加調試的難度:併發的複雜和調度的不可預測,會增加調試的困難。並且,許多調試工具都不能完全的支持併發程序的調試。

14    更多相關模式

譯者:這些模式許多我還也沒有接觸,準備逐個學習,高興的話還會翻譯
    監控者(Monitor)模式使用後,無論多少線程對一個被動對象調用,保證同時只有一個在實際執行。因爲更少的上下文切換和數據傳遞,這個模式比主動對象效率告。但此模式較難把客戶和服務線程分佈在不同機器上。
    反應堆(Reactor)模式,當不會再發生阻塞的時候,觸發多個事件處理器,分解和觸發任務。在存在回調機制的被動對象時,常用這個模式代替主動對象。也常常用它來連接主動對象和下面的半同步半異步模式一起使用。
    半同步半異步(Half-Sync/Half-Async)模式,這個摸索用來分離同步和異步調用。這個模式常常使用活動對象來實現異步任務層。
    命令處理器(Command Processor)模式,這個模式和主動對象差不多。它用來分離請求的發出和執行,一個命令處理器就相當於一個調度者。然而,他沒有代理者,客戶直接發佈命令。
    Broker模式,這個也和主動對象類似。主要的不同是,代理者和執行者是分佈邊界而不是線程邊界。
    互斥體(Mutex)模式。有時代替主動對象模式,簡單的在一個主動對象上加一個鎖,使其可以併發的被調用。他有多種實現方式,如重疊的鎖,支持權限繼承的鎖。
 
發佈了13 篇原創文章 · 獲贊 4 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章