原标题:Active Object -An Object Behavioral Pattern for Concurrent Programming
源文地址:http://www.cs.wustl.edu/~schmidt/PDF/Act-Obj.pdf
译者:cuichaox@gmail.com
译者注:
1. 对象分为主动对象和被动对象,主动对象内部包含一个线程,可以自动完成动作或改变状态,而一般的被动对象只能通过被其他对象调用才有所作为。在多线程程序中,经常把一个线程封装到主动对象里面。
2. 在翻译过程中,发现的原文不妥处被更正。
3. 原文中许多内容一再重复,颇有蛇足之感,取精用宏,删繁就简。
4. 尝试更高程度的意译。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test0.png)
我们的例子中,生产者和消费者使用TCP协议通信,这是面向连接的通信协议。网关上的进程向消费者发送数据时,操作会发生阻塞。这是因为,网络传输能力有限,TCP进行流量控制,防止过量的数据不能及时缓冲和处理。
要提高整体的效率,网关处理进程不能因一个连接上的阻塞而等待。另外,当生产者和消费者的数目增加的时候,整个进程必须相应的增加处理能力(译者注:通过增加处理线程)。
一个提高性能的有效方法,就是使用并行。应用并行后,服务对象拥有独立线程,线程实际完成操作,和方法调用的过程分开。并且,不同线程处理不同TCP连接,一个连接上的线程被阻塞,不会影响到其他连接的线程。
1. 对一个对象调用不能阻塞整个进程,不能影响其他线程的执行:比如,通信网关的例子中,一个TCP连接上的数据传送被阻塞,整个进程仍然能继续处理。同样,其他没有被阻塞的网络连接,应该正常的发送数据。
2. 对共享对象同步访问的逻辑应该简化:客户在使用共享对象地时候,如果面对底层的同步机制,必须记得先要获取互斥锁,后要释放互斥锁,编码就比较麻烦。一般情况,共享对象的方法隐藏这些细节,当多个客户线程访问同一个对象时,这些约束是透明的。
3. 存在可以平行进行的操作,设计成同时执行:在通信网关的例子中,同时使用多个TCP连接给不同的消费者发送数据。如果网关的进程在单个线程上执行,及时使用多个处理器不能明显的提高性能。
主 动对象的组成:一个代理者(Proxy)实现外部的访问接口;一个执行者(Servant)。代理和执行者在不同的线程执行,分离方法调用和执行的过程: 代理者在客户线程中被调用执行,执行者在另外的线程完成操作。运行时,代理者把客户的调用信息封装“调用请求”(Method Request),通过调度者(Scheduler)把这个请求放到一个活动队列(Activation Queue)。调度者和执行者运行在另外的线程中,这个线程启动后,不断地从活动队列中得到“调用请求”对象,派发给执行者完成客户请求的操作。客户调用 代理者后马上得到一个预约容器(Future),今后可以通过这个预约容器得到返回的结果。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test1.jpg)
在这个模式中,共有六个参与者。
代理者(Proxy)
代理者定义了被客户调用的接口。这个接口是函数调用的方式,而不是像传统线程通信,使用数据传递的方式。当函数被调用的,代理者构造一个“调用请求”对象,并把它放到活动队列中,这一切都发生在客户线程中。
调用请求(Method Request)
“调用请求”用来保存相关函数调用的部上下文信息,比如函数标识,函数的参数,这些信息将在不同线程间传递。一个抽象的“调用请求”类,定义了执行活动对 象方法的接口Call。并且包含一个Guard函数,Guard用来检查调用的条件是否满足。对代理者提供的每一个主动对象方法,在访问其执行者的时候需 要条件判断。代理者被调用的时候会创建具体“调用请求”对象,对象中包含了执行这个方法必须的参数和数据的返回方式。
活动队列(Activation Queue)
这个队列维护了一个缓冲区(译者注:不一定是先进先出的队列),缓冲区中存放了待执行的“调用请求”。正是这个队列分离可客户线程和执行操作的线程。
调度者(Scheduler)
调度者管理活动队列。调度者决定队列中的哪一个调用请求先被执行。调度的决定可能决定与多个规则:关键字顺序,入队的顺序,要满足的执行条件或等待发生的事件,如:在一个数据结构中出现空闲区。调度者使用Guard方法的调用,来检查是否满足执行条件。
执行者(Servant)
真正完成操作的对象。执行者实际完成代理者定义的主动对象方法,响应一个“调用请求”。调度者给“调用请求”分派一共执行者,然后调用“调用请求”中的 Call方法。对应的执行者的方法将被调用。执行者的方法运行在调度者的线程中。执行者可能同时提供了一些方法供“调用请求”实现Guard。
预约容器(Future)
当执行者完成操作,客户通过预约容器获取返回结果。当客户调用代理者的方法后,一共空预约容器马上返回。预约容器指向一块内存空间,用来保存返回结果。客户可以通过轮训或阻塞调用的方法,通过预约容器得到返回结果。
注×:原图中“enqueue(M1)”的位置有误,入队操作应该在返回Future之前,本图已经更正。黄色表现客户线程空间,绿色表示调度者线程空间。
1. 构造“调用请求”:在这个阶段,客户调用代理者的一个方法m1()。“调用请求”对象被创建,这个对象中包含了所有的参数。代理者把这个“调用请求”对象 传递给调度者,调度者把它入队到活动队列。如果方法m1()有返回值,就返回一个预约容器(Future),否则不返回。
2. 调度执行:调度者的执行线程中,监控活动队列,当队列中的一个请求满足执行条件时,调度者把它出队,把一个执行者绑定到这个请求上。然后通过“调用请求”的Call方法,Call再调用执行者的m1(),完成客户请求的操作。
3. 完成:在这个阶段,如果有返回值,就把返回值存储到对应的预约容器中。然后调度者线程继续在活动队列中查找下一个要执行的“调用请求”。客户就可以在预约容器中找到返回值。当“调用请求”和“预约容器”不在使用的时候,注意销毁,防止内存漏洞。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test3.png)
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);
每一个消费者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的处理。
在实现主动对象模式的时候,为了减少对象的个数。可以把代理者和执行者的角色都分派到调度者身上。甚至“调用请求”的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_;
// ...
};
这样集成后,减少了组件,实现更加简化。当然,这样也带来了缺点,调度者必须知道代理者和执行者的具体类型,具体实现。这样就很难在不同的活动对象中,重用调度者。
消息的直接传递:
更近一步的简化,代理者和执行者都删除掉。在客户线程和调度者线程之间直接使用数据的方式传递消息。
class 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);
分布式活动对象:
代理者和调度者之间跨过网络。代理者把要把“调用请求”对象序列化,然后通过网络传输给另外机器上的调度者,调度者接收并再造“调用请求”对象。
使用线程池:
使用线程池,可以让一个活动对象支持多个执行者。多个执行者提供相同的服务。每一个执行者运行在不同的线程中,由调度者统一调度,当有新的请求时,调度者马上安排一个工作线程工作。
2. ACE Framework
3. Siemens MedCom
4. Siemens Call Center management system:
5. Actors
译者:此节只列出他们的名字,感兴趣的同志请参考原文。和看《设计模式》的时候情况一样,我不太关注这一节。
1. 增强了程序的并行性,降低了同步的复杂性。客户线程和异步调起操作并行执行。同步的复杂性由调度者独立处理。
2. 让多个耗时的操作并行执行。只要软件和硬件支持,可以让多个活动的对象彼此不干扰地同时运行。
3. 方法的执行和调用的顺序可以不一致。方法的调用是异步调用。而方法的执行决定于如何调度。
当然,主动对象也有以下负面的影响
1. 性能过多消耗:系统消耗的程度决定于调度者的实现。用户态和系统态的上下文切换,同步信号的时候,数据的传送都会带来消耗。一般说来,主动对象模式适合大粒度的对象,对很小的对象使用这个模式容易带来性能的过度消耗。请和其它并发模式比较如监控者模式。
2. 增加调试的难度:并发的复杂和调度的不可预测,会增加调试的困难。并且,许多调试工具都不能完全的支持并发程序的调试。
监控者(Monitor)模式使用后,无论多少线程对一个被动对象调用,保证同时只有一个在实际执行。因为更少的上下文切换和数据传递,这个模式比主动对象效率告。但此模式较难把客户和服务线程分布在不同机器上。
反应堆(Reactor)模式,当不会再发生阻塞的时候,触发多个事件处理器,分解和触发任务。在存在回调机制的被动对象时,常用这个模式代替主动对象。也常常用它来连接主动对象和下面的半同步半异步模式一起使用。
半同步半异步(Half-Sync/Half-Async)模式,这个摸索用来分离同步和异步调用。这个模式常常使用活动对象来实现异步任务层。
命令处理器(Command Processor)模式,这个模式和主动对象差不多。它用来分离请求的发出和执行,一个命令处理器就相当于一个调度者。然而,他没有代理者,客户直接发布命令。
Broker模式,这个也和主动对象类似。主要的不同是,代理者和执行者是分布边界而不是线程边界。
互斥体(Mutex)模式。有时代替主动对象模式,简单的在一个主动对象上加一个锁,使其可以并发的被调用。他有多种实现方式,如重叠的锁,支持权限继承的锁。
[1] D. C. Schmidt, “Acceptor and Connector: Design Patterns for Initializing Communication Services,” in Pattern Languages of Program Design (R. Martin, F. Buschmann, and D. Riehle, eds.), Reading, MA: Addison-Wesley, 1997.
[2] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal, Pattern-Oriented Software Architecture - A System of Patterns. Wiley and Sons, 1996.
[3] D. C. Schmidt, “A Family of Design Patterns for Applicationlevel Gateways,” The Theory and Practice of Object Systems (Special Issue on Patterns and Pattern Languages), vol. 2, no. 1, 1996.
[4] W. R. Stevens, TCP/IP Illustrated, Volume 1. Reading, Massachusetts: Addison Wesley, 1993.
[5] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[6] Object Management Group, The Common Object Request Broker: Architecture and Specification, 2.2 ed., Feb. 1998.
[7] R. H. Halstead, Jr., “Multilisp: A Language for Concurrent Symbolic Computation,” ACM Trans. Programming Languages and Systems, vol. 7, pp. 501–538, Oct. 1985.
[8] B. Liskov and L. Shrira, “Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems,” in Proceedings of the SIGPLAN’88 Conference on Programming Language Design and Implementation, pp. 260–267, June 1988.
[9] D. C. Schmidt, “ACE: an Object-Oriented Framework for Developing Distributed Applications,” in Proceedings of the6th USENIX C++ Technical Conference, (Cambridge, Massachusetts),USENIX Association, April 1994.
[10] P. America, “Inheritance and Subtyping in a Parallel Object-Oriented Language,” in ECOOP’87 Conference Proceedings,pp. 234–242, Springer-Verlag, 1987.
[11] D. G. Kafura and K. H. Lee, “Inheritance in Actor-Based Concurrent Object-Oriented Languages,” in ECOOP’89 Conference Proceedings, pp. 131–145, Cambridge University Press, 1989.
[12] S. Matsuoka, K. Wakita, and A. Yonezawa, “Analysis of Inheritance Anomaly in Concurrent Object-Oriented Languages,” oOPS Messenger, 1991.
[13] M. Papathomas, “Concurrency Issues in Object-Oriented Languages,” in Object Oriented Development (D. Tsichritzis,ed.), pp. 207–245, Centre Universitaire D’Informatique, University of Geneva, 1989.
[14] W. R. Stevens, UNIX Network Programming, Second Edition. Englewood Cliffs, NJ: Prentice Hall, 1997.
[15] R. G. Lavender and D. G. Kafura, “A Polymorphic Future and First-Class Function Type for Concurrent Object-Oriented Programming in C++,” in Forthcoming, 1995.http://www.cs.utexas.edu/users/lavender/papers/futures.ps.
[16] D. Box, Essential COM. Addison-Wesley, Reading, MA,1997.
[17] D. C. Schmidt, D. L. Levine, and S. Mungee, “The Design and Performance of Real-Time Object Request Brokers,” ComputerCommunications, vol. 21, pp. 294–324, Apr. 1998.[18] D. C. Schmidt, “Evaluating Architectures for Multi-threaded CORBA Object Request Brokers,” Communications of the
ACM special issue on CORBA, vol. 41, Oct. 1998.[19] Object Management Group, CORBA Messaging Specification,OMG Document orbos/98-05-05 ed., May 1998.
[20] P. Jain, S. Widoff, and D. C. Schmidt, “The Design and Performanceof MedJava – Experience Developing Performance-Sensitive Distributed Applications with Java,” IEE/BCS DistributedSystems Engineering Journal, 1998.
[21] G. Agha, A Model of Concurrent Computation in Distributed Systems. MIT Press, 1986.
[22] C. Tomlinson and V. Singh, “Inheritance and Synchronization with Enabled-Sets,” in OOPSLA’89 Conference Proceedings,pp. 103–112, Oct. 1989.
[23] D. Kafura, M. Mukherji, and G. Lavender, “ACT++: A Class Library for Concurrent Programming in C++ using Actors,”
Journal of Object-Oriented Programming, pp. 47–56, October 1992.
[24] D. C. Schmidt, “Reactor: An Object Behavioral Pattern for Concurrent Event Demultiplexing and Event Handler Dispatching,”in Pattern Languages of Program Design (J. O.Coplien and D. C. Schmidt, eds.), pp. 529–545, Reading, MA: Addison-Wesley, 1995.
[25] D. C. Schmidt and C. D. Cranor, “Half-Sync/Half-Async: anArchitectural Pattern for Efficient and Well-structured Concurrent I/O,” in Proceedings of the 2nd Annual Conference on the Pattern Languages of Programs, (Monticello, Illinois),pp. 1–10, September 1995.
[26] Paul E. McKinney, “A Pattern Language for Parallelizing Existing Programs on Shared Memory Multiprocessors,” in Pattern Languages of Program Design (J. O. Coplien, J. Vlissides,and N. Kerth, eds.), Reading, MA: Addison-Wesley,1996.
源文地址:http://www.cs.wustl.edu/~schmidt/PDF/Act-Obj.pdf
译者:cuichaox@gmail.com
译者注:
1. 对象分为主动对象和被动对象,主动对象内部包含一个线程,可以自动完成动作或改变状态,而一般的被动对象只能通过被其他对象调用才有所作为。在多线程程序中,经常把一个线程封装到主动对象里面。
2. 在翻译过程中,发现的原文不妥处被更正。
3. 原文中许多内容一再重复,颇有蛇足之感,取精用宏,删繁就简。
4. 尝试更高程度的意译。
关键词
这 个文章介绍主动对象模式,主动对象是内部拥有自己的控制线程的对象。为了简化异步调用的复杂性,这个模式分离了方法的执行和调用。使用这个模式,一个对象 中无论是否有独立的线程,客户从外部访问它时,感觉是一样的。生产者/消费者,读者/写者,这两个应用广泛的模型中,这种策略非常适合。这个模式常用在多 线程的,分布式系统中。另外,一些客户端应用,如:视窗系统和网络浏览器,也可以使用主动对象模式,对并行的、异步调起的IO操作简化处理。
1 目的
主动对象模式隔离了方法执行和方法调用的过程,提高了并行性,对内部拥有控制线程的主动对象,降低了异步访问的复杂性。2 别名
并行行为对象(Concurrent Object and Actor)3 例子
为了说明主动对象模式,考虑一个通信网关的设计。网关隔离互相协作的多个组成单元,让交互过程不直接依赖于对方。参照图1,一个分布式系统中,来自多个生产者(卫星设备)的消息,被网管转发给多个消费者(局域网内的主机)。![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test0.png)
我们的例子中,生产者和消费者使用TCP协议通信,这是面向连接的通信协议。网关上的进程向消费者发送数据时,操作会发生阻塞。这是因为,网络传输能力有限,TCP进行流量控制,防止过量的数据不能及时缓冲和处理。
要提高整体的效率,网关处理进程不能因一个连接上的阻塞而等待。另外,当生产者和消费者的数目增加的时候,整个进程必须相应的增加处理能力(译者注:通过增加处理线程)。
一个提高性能的有效方法,就是使用并行。应用并行后,服务对象拥有独立线程,线程实际完成操作,和方法调用的过程分开。并且,不同线程处理不同TCP连接,一个连接上的线程被阻塞,不会影响到其他连接的线程。
4 场景
对象的访问者(Client,下统称为客户)和对象的实现在不同的线程中。5 问题
许多应用场景,服务者并行处理多客户端的请求,提高服务的质量(QoS)。被动对象在客户线程中完成操作过程,主动对象使用专用的线程完成。一个对象的数据被多个线程共享时,必须处理好线程的同步。这导致三个约束。1. 对一个对象调用不能阻塞整个进程,不能影响其他线程的执行:比如,通信网关的例子中,一个TCP连接上的数据传送被阻塞,整个进程仍然能继续处理。同样,其他没有被阻塞的网络连接,应该正常的发送数据。
2. 对共享对象同步访问的逻辑应该简化:客户在使用共享对象地时候,如果面对底层的同步机制,必须记得先要获取互斥锁,后要释放互斥锁,编码就比较麻烦。一般情况,共享对象的方法隐藏这些细节,当多个客户线程访问同一个对象时,这些约束是透明的。
3. 存在可以平行进行的操作,设计成同时执行:在通信网关的例子中,同时使用多个TCP连接给不同的消费者发送数据。如果网关的进程在单个线程上执行,及时使用多个处理器不能明显的提高性能。
6 方案
对每一个要求并发执行的对象,分离其方法的调用和执行。这样,这个对象的客户就像是调用一个常规的方法一样。这个方法,自动把任务交给另外的线程完成执行。主 动对象的组成:一个代理者(Proxy)实现外部的访问接口;一个执行者(Servant)。代理和执行者在不同的线程执行,分离方法调用和执行的过程: 代理者在客户线程中被调用执行,执行者在另外的线程完成操作。运行时,代理者把客户的调用信息封装“调用请求”(Method Request),通过调度者(Scheduler)把这个请求放到一个活动队列(Activation Queue)。调度者和执行者运行在另外的线程中,这个线程启动后,不断地从活动队列中得到“调用请求”对象,派发给执行者完成客户请求的操作。客户调用 代理者后马上得到一个预约容器(Future),今后可以通过这个预约容器得到返回的结果。
7 结构
下面使用Booch风格的类图,对主动对象的组成结构进行说明。(译者注:这是Booch在《面向对象的分析和设计》书中使用的类图风格)![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test1.jpg)
在这个模式中,共有六个参与者。
代理者(Proxy)
代理者定义了被客户调用的接口。这个接口是函数调用的方式,而不是像传统线程通信,使用数据传递的方式。当函数被调用的,代理者构造一个“调用请求”对象,并把它放到活动队列中,这一切都发生在客户线程中。
调用请求(Method Request)
“调用请求”用来保存相关函数调用的部上下文信息,比如函数标识,函数的参数,这些信息将在不同线程间传递。一个抽象的“调用请求”类,定义了执行活动对 象方法的接口Call。并且包含一个Guard函数,Guard用来检查调用的条件是否满足。对代理者提供的每一个主动对象方法,在访问其执行者的时候需 要条件判断。代理者被调用的时候会创建具体“调用请求”对象,对象中包含了执行这个方法必须的参数和数据的返回方式。
活动队列(Activation Queue)
这个队列维护了一个缓冲区(译者注:不一定是先进先出的队列),缓冲区中存放了待执行的“调用请求”。正是这个队列分离可客户线程和执行操作的线程。
调度者(Scheduler)
调度者管理活动队列。调度者决定队列中的哪一个调用请求先被执行。调度的决定可能决定与多个规则:关键字顺序,入队的顺序,要满足的执行条件或等待发生的事件,如:在一个数据结构中出现空闲区。调度者使用Guard方法的调用,来检查是否满足执行条件。
执行者(Servant)
真正完成操作的对象。执行者实际完成代理者定义的主动对象方法,响应一个“调用请求”。调度者给“调用请求”分派一共执行者,然后调用“调用请求”中的 Call方法。对应的执行者的方法将被调用。执行者的方法运行在调度者的线程中。执行者可能同时提供了一些方法供“调用请求”实现Guard。
预约容器(Future)
当执行者完成操作,客户通过预约容器获取返回结果。当客户调用代理者的方法后,一共空预约容器马上返回。预约容器指向一块内存空间,用来保存返回结果。客户可以通过轮训或阻塞调用的方法,通过预约容器得到返回结果。
8 运行
下面的图说明了一个调用过程中的三个阶段。注×:原图中“enqueue(M1)”的位置有误,入队操作应该在返回Future之前,本图已经更正。黄色表现客户线程空间,绿色表示调度者线程空间。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test2.png)
1. 构造“调用请求”:在这个阶段,客户调用代理者的一个方法m1()。“调用请求”对象被创建,这个对象中包含了所有的参数。代理者把这个“调用请求”对象 传递给调度者,调度者把它入队到活动队列。如果方法m1()有返回值,就返回一个预约容器(Future),否则不返回。
2. 调度执行:调度者的执行线程中,监控活动队列,当队列中的一个请求满足执行条件时,调度者把它出队,把一个执行者绑定到这个请求上。然后通过“调用请求”的Call方法,Call再调用执行者的m1(),完成客户请求的操作。
3. 完成:在这个阶段,如果有返回值,就把返回值存储到对应的预约容器中。然后调度者线程继续在活动队列中查找下一个要执行的“调用请求”。客户就可以在预约容器中找到返回值。当“调用请求”和“预约容器”不在使用的时候,注意销毁,防止内存漏洞。
9 实现
这一节说明一个主动对象模式使用的过程。这个应用是上面例子的部分实现。图2说明了各个组成部分。这节的例子,使用到了ACE框架的可重用组件。ACE提供了丰富的包装器和框架组件,用来完成软件间的通信任务,并且ACE是跨平台的。![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test3.png)
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连接把数据送出。![](https://p-blog.csdn.net/images/p_blog_csdn_net/cuichaox/test4.png)
每一个消费者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_;
// ...
};
这样集成后,减少了组件,实现更加简化。当然,这样也带来了缺点,调度者必须知道代理者和执行者的具体类型,具体实现。这样就很难在不同的活动对象中,重用调度者。
消息的直接传递:
更近一步的简化,代理者和执行者都删除掉。在客户线程和调度者线程之间直接使用数据的方式传递消息。
class 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 ORBS2. 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)模式。有时代替主动对象模式,简单的在一个主动对象上加一个锁,使其可以并发的被调用。他有多种实现方式,如重叠的锁,支持权限继承的锁。
15 感谢
原作者感谢了很多人,我这里感谢一下原作者。感谢其分享知识的精神。16 参考书目
原文一共列举了26本书。真多呀。也复制过来,作为找书的信息[1] D. C. Schmidt, “Acceptor and Connector: Design Patterns for Initializing Communication Services,” in Pattern Languages of Program Design (R. Martin, F. Buschmann, and D. Riehle, eds.), Reading, MA: Addison-Wesley, 1997.
[2] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal, Pattern-Oriented Software Architecture - A System of Patterns. Wiley and Sons, 1996.
[3] D. C. Schmidt, “A Family of Design Patterns for Applicationlevel Gateways,” The Theory and Practice of Object Systems (Special Issue on Patterns and Pattern Languages), vol. 2, no. 1, 1996.
[4] W. R. Stevens, TCP/IP Illustrated, Volume 1. Reading, Massachusetts: Addison Wesley, 1993.
[5] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[6] Object Management Group, The Common Object Request Broker: Architecture and Specification, 2.2 ed., Feb. 1998.
[7] R. H. Halstead, Jr., “Multilisp: A Language for Concurrent Symbolic Computation,” ACM Trans. Programming Languages and Systems, vol. 7, pp. 501–538, Oct. 1985.
[8] B. Liskov and L. Shrira, “Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems,” in Proceedings of the SIGPLAN’88 Conference on Programming Language Design and Implementation, pp. 260–267, June 1988.
[9] D. C. Schmidt, “ACE: an Object-Oriented Framework for Developing Distributed Applications,” in Proceedings of the6th USENIX C++ Technical Conference, (Cambridge, Massachusetts),USENIX Association, April 1994.
[10] P. America, “Inheritance and Subtyping in a Parallel Object-Oriented Language,” in ECOOP’87 Conference Proceedings,pp. 234–242, Springer-Verlag, 1987.
[11] D. G. Kafura and K. H. Lee, “Inheritance in Actor-Based Concurrent Object-Oriented Languages,” in ECOOP’89 Conference Proceedings, pp. 131–145, Cambridge University Press, 1989.
[12] S. Matsuoka, K. Wakita, and A. Yonezawa, “Analysis of Inheritance Anomaly in Concurrent Object-Oriented Languages,” oOPS Messenger, 1991.
[13] M. Papathomas, “Concurrency Issues in Object-Oriented Languages,” in Object Oriented Development (D. Tsichritzis,ed.), pp. 207–245, Centre Universitaire D’Informatique, University of Geneva, 1989.
[14] W. R. Stevens, UNIX Network Programming, Second Edition. Englewood Cliffs, NJ: Prentice Hall, 1997.
[15] R. G. Lavender and D. G. Kafura, “A Polymorphic Future and First-Class Function Type for Concurrent Object-Oriented Programming in C++,” in Forthcoming, 1995.http://www.cs.utexas.edu/users/lavender/papers/futures.ps.
[16] D. Box, Essential COM. Addison-Wesley, Reading, MA,1997.
[17] D. C. Schmidt, D. L. Levine, and S. Mungee, “The Design and Performance of Real-Time Object Request Brokers,” ComputerCommunications, vol. 21, pp. 294–324, Apr. 1998.[18] D. C. Schmidt, “Evaluating Architectures for Multi-threaded CORBA Object Request Brokers,” Communications of the
ACM special issue on CORBA, vol. 41, Oct. 1998.[19] Object Management Group, CORBA Messaging Specification,OMG Document orbos/98-05-05 ed., May 1998.
[20] P. Jain, S. Widoff, and D. C. Schmidt, “The Design and Performanceof MedJava – Experience Developing Performance-Sensitive Distributed Applications with Java,” IEE/BCS DistributedSystems Engineering Journal, 1998.
[21] G. Agha, A Model of Concurrent Computation in Distributed Systems. MIT Press, 1986.
[22] C. Tomlinson and V. Singh, “Inheritance and Synchronization with Enabled-Sets,” in OOPSLA’89 Conference Proceedings,pp. 103–112, Oct. 1989.
[23] D. Kafura, M. Mukherji, and G. Lavender, “ACT++: A Class Library for Concurrent Programming in C++ using Actors,”
Journal of Object-Oriented Programming, pp. 47–56, October 1992.
[24] D. C. Schmidt, “Reactor: An Object Behavioral Pattern for Concurrent Event Demultiplexing and Event Handler Dispatching,”in Pattern Languages of Program Design (J. O.Coplien and D. C. Schmidt, eds.), pp. 529–545, Reading, MA: Addison-Wesley, 1995.
[25] D. C. Schmidt and C. D. Cranor, “Half-Sync/Half-Async: anArchitectural Pattern for Efficient and Well-structured Concurrent I/O,” in Proceedings of the 2nd Annual Conference on the Pattern Languages of Programs, (Monticello, Illinois),pp. 1–10, September 1995.
[26] Paul E. McKinney, “A Pattern Language for Parallelizing Existing Programs on Shared Memory Multiprocessors,” in Pattern Languages of Program Design (J. O. Coplien, J. Vlissides,and N. Kerth, eds.), Reading, MA: Addison-Wesley,1996.