第1章 ACE自适配通信环境:用于开发通信软件的面向对象网络编程工具包
Douglas C. Schmidt
ACE自适配通信环境(Adaptive Communication Environment)是一种面向对象(OO)的工具包,它实现了通信软件的许多基本的设计模式。ACE的目标用户是在UNIX和Win32平台上开发高性能通信服务和应用的开发者。ACE简化了使用进程间通信、事件多路分离、显式动态链接和并发的OO网络应用和服务的开发。通过在运行时将服务与应用动态链接进应用,并在一个或多个进程或线程中执行这些服务,ACE使系统的配置和重配置得以自动化。
本论文描述ACE的结构和功能,并使用来自像电信、企业级医学成像和WWW服务这样的领域的例子阐释核心的ACE特性。ACE可以自由使用,并正在被用于许多商业项目(比如爱立信、Bellcore、西门子、摩托罗拉、柯达,和McDonnell Douglas),以及许多学院和工业研究项目。ACE已被移植到多种OS(操作系统)平台上,包括Win32和大多数的UNIX/POSIX实现。此外,同时有C++和Java版本的ACE可用。
对健壮的和高性能的分布式计算系统的需求一直在稳定地增长。这些类型的系统的例子包括全球个人通信系统、网络管理平台,企业级医学成像系统、在线金融分析系统,以及实时航空控制系统。对于以下方面来说,分布式计算是一种有前途的技术:通过连接性和相互配合促进协作;通过并行处理改善性能;通过复制改善可靠性和可用性;通过模块性改善可伸缩性和可移植性;通过动态配置和重配置改善可扩展性;以及通过资源共享和开发系统提高成本效用。
尽管分布式计算提供了许多潜在的好处,开发通信软件仍然是昂贵而易错的。面向对象编程语言、组件和构架(Framework)是被广泛鼓吹的、用以降低软件成本并提高软件质量的技术。去除那些过分的宣传,OO的主要好处源于对模块性和可扩展性的强调,它将易变的实现细节封装在稳定的接口后面,并增强了软件的复用。
多年来,在某些已被广泛探索的领域中的开发者已经成功地应用了OO技术和工具。例如,Microsoft MFC GUI构架和OCX组件是PC平台上用于创建图形商业应用的事实上的工业标准。尽管这些工具有着自身的局限,它们仍然演示了复用通用构架和组件的生产效率优势。
在像电信、医学成像、航空控制和在线事务处理这样的更复杂的领域中,软件开发者历来就缺少标准的、成型的中间件组件。结果,开发者在很大程度上是从头开始构建、验证和维护软件系统。在一个政府经济干预减少的艰难的全球竞争时代,这样的作坊式开发过程正在变得难以容忍的昂贵和费时。在业界,这样的情形导致了一场“分布式软件危机”:计算硬件和网络在变小、变快、变得更为便宜;而分布式软件的开发和维护在变大、变慢、变得更为昂贵。
构建分布式软件的挑战源于与分布式系统相关联的固有的和非固有的复杂性[1]。固有的复杂性源于开发分布式软件的基本的挑战,其中主要的有:检测和恢复网络及主机失败、最小化通信响应延迟的影响,以及确定服务组件和工作负载在网络的处理单元上的最优划分。
非固有的复杂性源于用以开发分布式软件的工具和技术的局限。例如,许多标准的网络机制(比如socket[2]和TLI[3])和可复用组件库(比如X windows和Sun RPC)缺乏类型安全的、可移植的、可重入的和可扩展的应用编程接口(API)。同样地,通用网络编程接口,如socket和TLI,使用弱类型的整型句柄,可能会导致微妙的运行时错误[4]。
复杂性的另一来源起因于算法分解的普遍使用[5],它致使软件系统不可扩展和不可复用[6]。尽管图形用户接口(GUI)普遍采用面向对象技术构建,典型的分布式软件通常仍然使用算法分解进行开发。在一些流行的网络编程教科书[7, 8, 3]中的例子基于面向算法的设计和实现技术,从而更加恶化了前述问题。
可扩展性和最大限度复用的缺乏对于复杂的分布式软件是特别成问题的。可扩展性是确保服务和特性的及时修改和增强的基本要求。复用是有效利用专家开发者的领域知识、以避免重新开发和重新验证“反复出现的需求和软件挑战的通用解决方案”的基本要求。
面向对象的设计模式和构架有助于减少对分布式软件的核心概念和抽象的昂贵的重新发现和发明,它们因此而备受重视。模式提供了一种封装设计知识的方法,这些设计知识为标准的分布式软件开发问题提供解决方案[9]。例如,模式对于描述重复出现的“微型结构”(比如反应堆(Reactor)[10]和主动对象(Active Object)[11])十分有用,这些微型结构是对一些已被证明可用于构建分布式通信软件的通用对象结构的抽象。但是,被文档化为模式的抽象并不直接产生可复用代码。因此,有必要通过构架的创建和使用来增加对模式的研究。
通过集成成组的抽象类,并定义这些类的协作的标准途径,构架为应用提供了可复用的软件组件[12]。构架实例化设计模式族,以帮助开发者避免对通用分布式软件组件的昂贵的重新发明。其成果是“半完成”的应用骨架,它可以通过继承和实例化构架中的可复用“积木”组件来进行定制。因为构架与关键的分布式编程任务(比如服务初始化、错误处理、流控制、事件多路分离、并发控制)紧密地集成在一起,复用的范围可以显著地大于使用传统函数库,甚或是通常的OO类库。
本论文被组织如下:1.2给出ACE工具包的结构和功能的综述;1.3详细描述ACE C++包装组件和较高级的ACE构架组件及模式;1.4检查若干使用ACE构建的网络应用的实现;还有1.5给出结束语
为阐释OO模式和构架是怎样被成功地应用于分布式软件的,本论文考查自适配通信环境(ACE)[6]。ACE是可以自由使用的OO工具包,其中包含有丰富的、可跨越广泛的OS平台执行通用网络编程任务的可复用包装、类属,以及构架。ACE提供的任务包括:

图1-1 ACE自适配通信环境中的组件
ACE工具包的设计采用分层的体系结构。图1-1演示了ACE组件间的纵向和横向关系。ACE的较低层是封装现有的OS网络编程机制的OO包装(wrapper)。ACE的高层扩展这些包装,以提供OO构架和组件、覆盖更为广泛的面向应用的网络任务和服务。这一部分的余下部分给出对ACE中类属的结构和功能的综述(如图1-2所示)。1.3提供了对ACE的网络编程特性和组件的深入讨论。
贯穿本论文,ACE组件通过Booch表示法[5]来进行图解。实心矩形表示类属,它将一定数量的相关类合成进一个公共的名字空间。实心云表示对象;如嵌套则表示对象间的合成关系;而无方向的边表示在两个对象间存在某种类型的链接。虚线云表示类;有向边表示类之间的继承关系;而一端有小圆圈的无向边表示两个类之间的合成或是使用关系。在三角形内标记的“A”标识一个类为抽象类[25]。抽象类不能被直接实例化,而必须被子类化。在实例化抽象类子类的任何对象之前,该子类必须提供所有抽象方法的定义。
ACE的源码树含有超过85,000行C++代码。其中大约9,000行代码(也就是,大约为总数的10%)为OS适配层所特有。该层将ACE的较高层和与下列OS机制相关联的平台特有的依赖屏蔽开来:

图1-2 ACE中的类属
在OS适配层之上是许多OO包装,它们封装并增强在像Win32和UNIX这样的现代操作系统上可用的并发、进程间通信(IPC)、以及虚拟内存机制(在图1-1底部演示)。应用可以通过有选择地继承、聚合(aggregating)、和/或实例化下列ACE包装类属来合并和编写这些组件:
通过采用类型安全的OO接口封装OS通信、并发和虚拟内存机制,OO包装的使用提高了应用的健壮性。这也减少了应用直接访问用弱类型C接口编写的底层OS库的需求。因此,像C++和Java这样的OO语言的编译器可以在编译时、而非运行时检测类型系统违例。ACE的C++版本大量使用内联(inlining),以消除包装层提供额外的类型安全性和抽象所导致的性能下降。
ACE含有一个更高层的网络编程构架,集成并增强了较低层的OS包装。该构架支持由应用服务组成的并发网络看守的动态配置。ACE的构架部分包括以下类属:
除了包装和构架,ACE还提供了一个网络服务组件的标准库。这些组件在ACE中扮演两种角色:
当与OO语言特性(比如类、继承、动态绑定和参数化类型)和设计模式(比如抽象工厂(Abstract Factory)、构建器(Builder)和服务配置器)相结合时,可复用的ACE组件促进了通信服务和应用的开发,无需修改、重编译、重链接、甚至不用重启运行中的软件,它们就可以被更新和扩展[20]。
ACE提供了植根于IPC SAP(“进程间通信服务访问点”)基类的一个类属“森林”。标准的基于I/O句柄的OS本地和远地IPC机制提供了面向连接和无连接的协议,IPC SAP对这些机制进行了封装。如图1-3所示,该类属“森林”包括SOCK SAP(封装socket API)、TLI SAP(封装TLI API)、SPIPE SAP(封装UNIX SunOS 5.x STREAM管道API),以及FIFO SAP(封装UNIX命名管道API)。

图1-3 IPC SAP类属关系
每一类属都被组织成一个继承层次。所有子类都提供定义良好的、本地或远地通信机制的子集的接口。在一个层次里的子类共同包含了一种特定通信抽象的全部功能(比如Internet域或UNIX域协议族)。类的使用(相对于单独的函数)帮助简化了网络编程:
下面的部分讨论IPC SAP中的每个类属。

图1-4 SOCK SAP类属
SOCK SAP[4]类属为应用提供Internet域和UNIX域协议族[8]的面向对象的接口。通过继承或实例化图1-4所示的适当的SOCK SAP子类,应用可以访问底层的Internet域或UNIX域的socket类型的功能。ACE_SOCK *子类封装Internet域的功能,而ACE_LSOCK *子类封装UNIX域的功能。如图1-4所示,这些子类可进一步划分为(1)*Dgram组件(提供不可靠、无连接、面向消息的功能) vs. *ACE_Stream组件(提供可靠、面向连接的字节流的功能)和(2)ACE_*_Acceptor组件(提供通常用于服务器的连接建立功能) vs. *Stream组件(提供同时用于客户和服务器的双向字节流传输功能)。
使用OO包装来封装socket接口有助于(1)在编译时检测许多微妙的应用类型系统违例,(2)推动传输层接口的平台无关性,以改善应用的可移植性,以及(3)极大地减少应用代码的数量和花费在较低级网络编程细节上的开发工作。为演示后面一点,下面的例子程序实现了一个简单的客户应用,使用ACE_SOCK_Dgram_Bcast类来向局域网子网中所有在指定的端口号上侦听的服务器广播消息:
int main (int
argc, char *argv[])
{
ACE_SOCK_Dgram_Bcast
b_sap (sap_any);
char *msg;
unsigned short
b_port;
msg = argc >
1 ? argv[1] : "hello world\n";
b_port = argc
> 2 ? atoi (argv[2]) : 12345;
if (b_sap.send
(msg, strlen (msg), b_port) == -1)
perror ("can’t send
broadcast"), exit (1);
exit (0);
}
把这个简洁的例子与直接使用socket接口实现广播所需的成打的C源码相比较很有启发意义。
TLI SAP类属提供系统V传输层接口(Transport Layer Interface)的OO接口。TLI的TLI SAP继承层次几乎与socket的SCOK SAP包装相同。主要的区别是TLI和TLI SAP并不定义UNIX域协议族的接口。此外,目前TLI没有被移植到Win32平台。
通过结合C++特性(比如缺省参数值和模板)和tirdwr(read/write兼容性STREAM模块),开发可在编译时参数化、以在基于socket或基于TLI传输接口上正确操作的应用变得相对直截了当了。例如,下面的代码演示怎样将C++模板用于参数化应用所需的IPC机制。这些代码是从在1.4.1中描述的分布式日志工具中摘录出来的。在下面的代码中,一个派生自ACE_Event_Handler的子类被一种特定类型的传输接口及其相应的协议地址类参数化:
/*
Logging_Handler header file */
template
<class PEER_STREAM, class ADDR>
class Logging_Handler
: public ACE_Event_Handler
{
public:
Logging_Handler
(void);
virtual
?Logging_Handler (void);
virtual int
handle_input (ACE_HANDLE);
virtual
ACE_HANDLE get_handle (void) const
{
return
this->peer_stream_.get_handle ();
}
protected:
PEER_STREAM
peer_stream_;
}
如下所示,取决于底层OS平台的特定属性(比如是基于BSD的SunOS 4.x还是基于系统V的SunOS 5.x),日志应用可以实例化Client Handler类、以使用SOCK SAP或是TLI SAP:
/* Logging
application */
class
Logging_Handler
#if defined
(MT_SAFE_SOCKETS)
: public
Logging_Handler<ACE_SOCK_Stream, ACE_INET_Addr>
#else
: public
Logging_Handler<ACE_TLI_Stream, ACE_INET_Addr>
#endif /*
MT_SAFE_SOCKETS */
{
/* ... */
};
在开发必须具有跨平台可移植性的应用时,这种基于模板的方法所带来的更多的灵活性极其有用。特别地,因为SunOS 5.2的socket实现不是线程安全的,而SunOS 4.x的TLI实现含有许多严重缺陷,在SunOS的变种上进行跨平台开发时,必须具有根据传输接口来参数化应用的能力。
TLI SAP还使应用与许多TLI接口的特性相屏蔽。例如,在一个并发服务器中,在qlen > 1[3]的情况下,要正确处理t_listen和t_accept的非直观和易错的行为,需要编写一些微妙的应用级代码;而在TLI Acceptor类的accept方法中这些代码被封装起来。该方法接受来自客户的到来的连接请求。通过使用C++缺省参数值,基于TLI SAP和基于SOCK SAP的应用的调用accept方法的标准方法在语法上是相同的。
SPIPE SAP类属为高性能本地IPC提供OO包装。在Win32平台上,SPIPE SAP类属在命名管道之上实现。Win32命名管道机制主要用于在同一机器的进程间高效地传输数据。对于本地IPC,它通常比使用socket更为高效[27]
在UNIX平台上,SPIPE SAP类属通过已安装(mounted)的STREAM管道和connld[28]来实现。SunOS 5.x提供fattach系统调用,将管道句柄安装到UNIX文件系统中的指定位置。通过将connld流模块推入管道已安装的一端,可以创建服务器应用。当与服务器运行在同一主机上的客户应用随后打开与已安装的管道相关联的文件时,客户和服务器都将获得一个I/O句柄,标识一个唯一的、非多路服用和双向的通信信道。
SPIPE SAP继承层次是SOCK SAP及TLI SAP所用层次的“镜像“。它提供与SOCK SAP ACE_LSOCK *类(它们自己封装了UNIX域的socket)相近似的功能。但是,在SunOS 5.x平台上,SPIPE SAP比ACE_LSOCK*接口更为灵活,因为它使得STREAM模块可以分别被“推入”和“弹出”SPIPE SAP终点。SPIPE SAP还支持在同一主机上执行的进程和/或线程间的字节流和按优先级排序的面向消息的数据的双向传送[29]。
FIFO SAP类属封装UNIX命名管道机制(也称为FIFO)。不像STREAM管道,命名管道只提供从一或多个发送者到单一接收者的单向数据信道。而且,来自不同发送者的消息都被放入同一个通信信道。因而,在每一消息中必须明确地包含某种类型的多路分离标识符(demultiplexing identifier),以使接收者能够确定是哪一个发送者发送的消息。
SunOS 5.x中的基于STREAM的命名管道同时实现提供面向消息和面向字节流的数据传送机制。相反,某些平台(比如SunOS 4.x)只提供面向字节流的命名管道。因此,除非总是使用定长消息,在SunOS 4.x中通过命名管道发送的消息必须通过某种形式的字节计数、或是特别的结束符来加以区分,以使接收者能从通信信道字节流中提取消息。为减少这样的局限,ACE FIFO SAP实现包含有对SunOS 5.x的面向消息的机制进行模拟的逻辑。
除了封装像socket和TLI这样的基于句柄的I/O通信机制,ACE还提供了内存映射文件和系统V UNIX IPC机制的OO封装:
现有的Win32和UNIX的内存映射文件接口的风格有一点巴洛克(baroque,过分雕饰)。例如,开发者必须手工完成许多“簿记”工作(比如显式地打开文件、确定它的长度、执行多种映射,等等)。相反,ACE_Mem_Map OO封装提供的接口采用了缺省值和多种有若干类型特征(type signature)变体的构造器(例如,“从已打开的文件句柄映射”、“从文件名映射”,等等),以简化常见的内存映射文件的使用模式。
例如,下面的程序使用ACE_Mem_Map OO封装来映射一个通过命令行指定的文件,并将它的各行反向打印出来:
static void
putline (const char *s)
{
while (putchar
(*s++) != ’\n’)
continue;
}
int main (int
argc, char *argv[])
{
char *filename
= argv[1];
char *file_p;
Mem_Map mmap (filename);
if (mmap
(file_p) != -1)
{
size_t size = mmap.size () - 1;
if (file_p[size] == ’\0’)
file_p[size] = ’\n’;
while (--size >= 0)
if (file_p[size] == ’\n’)
putline (file_p + size + 1);
putline (file_p);
return 0;
}
else
return 1;
}
把这个OO包装接口的使用与直接使用像read这样的I/O系统调用所需的远为冗长的C接口相比较,很有启发意义。
ACE系统V IPC包装接口将开发者与无数多余的细节屏蔽开来。例如,对于利用标准wait和signal信号量的应用来说,ACE OO包装版本的系统V IPC信号量使用起来更为直观和简单;如下面的来自典型的生产者/消费者例子的代码片段所示:
typedef
ACE_SV_Semaphore_Simple SEMA;
SEMA prod (1,
SEMA::CREATE, 1);
SEMA cons (2,
SEMA::CREATE, 0);
void producer
(void)
{
for (;;)
{
prod.wait ();
// produce resource...
cons.signal ();
}
}
void consumer
(void)
{
for (;;)
{
cons.wait ();
// consume resource...
prod.signal ();
}
}
把这个简洁的OO包装接口与直接使用系统V信号量所需的远为冗长的C接口相比较,很有启发意义。
1.3.2
反应堆(Reactor):事件多路分离和事件处理器分派
通信软件对许多不同种类的事件(比如基于定时器、基于I/O、基于信号和基于同步的事件)进行多路分离和处理。例如,WWW服务器通常在内部使用事件循环、监控一个周知的Internet端口(通常是端口80)。该端口与一个应用特有的处理器(handler)相关联,此处理器侦听客户在端口80上的连接。当客户连接时,WWW服务器接受连接,并创建一个事件处理器为此HTTP请求服务。例如,如果Netscape浏览器发送一个GET请求,WWW服务器将返回所请求的内容给浏览器。
为统一并自动化事件驱动的处理活动,ACE提供了一个事件多路分离和事件处理器分派构架,称为ACE_Reactor[10]。Reactor将UNIX和Windows NT事件多路分离机制(比如select和poll)的功能封装在一个可移植和扩展的OO包装中[10]。这些OS事件多路分离系统调用检测在一或更多I/O句柄上同时发生的不同类型的输入和输出事件。
为改善应用的可移植性,不管使用了何种事件多路分离机制,ACE_Reactor都提供同样的接口。另外,ACE_Reactor还封装了在多线程事件处理环境中正确而高效地执行回调方式的分派所必需的互斥机制。

图1-5 ACE_Reactor类属组件
ACE_Reactor中的对象的结构在图1-5中演示。这些对象负责(1)事件(比如定时器驱动的呼出队列生成的临时事件、通信端口上收到的I/O事件,以及信号事件)的多路分离和(2)分派预登记事件处理器的适当方法,以处理这些事件。如图1-5所示,所有事件处理器对象都派生自ACE_Event_Handler抽象基类。该类指定一个被用于ACE_Reactor接口,以针对特定事件的到达、分派特定的应用特有的方法。
ACE_Reactor使用在Event Handler中声明的虚方法,以集成基于I/O句柄、基于定时器,和基于信号的事件的多路分离。基于I/O句柄的事件经由handle_input、handle_output和handle_exceptions方法分派;基于定时器的事件经由handle_timeout方法分派;而基于信号的事件经由handle_signal分派。
ACE_Event_Handler的子类(比如1.4.1描述的Logging_Handler)可以通过定义另外的方法和数据成员来扩展基类的接口。此外,ACE_Event_Handler接口中的虚方法可以有选择地被子类重载,以实现应用特有的功能。例如,在1.4.2介绍的PBX监控服务器中,应用特有的子类定义了Event Handler对象,通过继承和/或实例化1.3.1描述的SOCK SAP或TLI SAP传输接口类的对象,与客户进行通信。在ACE_Event_Handler基类中的虚方法被子类定义后,应用可以实例化所得的事件处理器对象。
下面的例子实现一个简单的程序,它使用双向通信信道,在两个进程间持续地前后交换消息。此例演示服务是怎样从ACE_Event_Handler继承的。它还描述ACE_Reactor怎样被用于多路分离和分派基于I/O、基于信号和基于定时器的事件。下面所示的Ping_Pong类从ACE_Event_Handler继承接口,并实现它自己的应用特有的功能:
class Ping_Pong
: public ACE_Event_Handler
{
public:
Ping_Pong (char
*b)
: len (min (strlen (b) + 1,
BUFSIZ))
{
strncpy (this->buf, b, BUFSIZ);
}
virtual int
handle_input (ACE_HANDLE handle)
{
return read (handle, this->buf,
BUFSIZ);
}
virtual int
handle_output (ACE_HANDLE handle)
{
return write (handle, this->buf,
this->len);
}
virtual int
handle_signal (int signum)
{
this->finished = 1;
}
virtual int
handle_timeout (const Time_Value &, const void *)
{
this->finished = 1;
}
bool done
(void)
{
return this->finished == 1;
}
private:
sig_atomic_t
finished;
char
buf[BUFSIZ];
size_t len;
};
双向通信信道使用SVR4
UNIX STREAM管道创建:
static int
init_handles (ACE_HANDLE handles[])
{
if (pipe
(handles) == -1)
LM_ERROR ((LOG_ERROR, "%p\n%a",
"pipe", 1));
// Enable message-oriented mode
instead of
// bytestream
mode.
int arg =
RMSGN;
if (ioctl
(handles[0], I_SRDOPT, arg) == -1
|| ioctl (handles[1], I_SRDOPT,
arg) == -1)
return -1;
}
主程序通过打开适当的通信信道开始运行。接着,程序派生一个子进程,在两个进程中各实例化一个名为callback的Ping_Pong事件处理器对象,为基于I/O、基于信号,和基于定时器的事件而将callback对象登记到ACE_Reactor的一个实例上,然后进入事件循环。如下所示:
int main (int
argc, char *argv[])
{
ACE_HANDLE
handles[2];
ACE_Reactor
reactor;
init_handles
(handles);
pid_t pid =
fork ();
Ping_Pong
callback (argv[1]);
// Register
I/O-based event handler
reactor.register_handler
(
handles[pid == 0],
&callback,
ACE_Event_Handler::READ_MASK
| ACE_Event_Handler::WRITE_MASK);
// Register
signal-based event handler
reactor.register_handler
(SIGINT, &callback)
// Register
timer-based event handler
reactor.schedule_timer
(&callback, 0, 10)
/* Main event
loop (run in each process) */
while
(callback.done () == false)
reactor.handle_events ();
return 0;
}
基于定时器和基于信号的事件的callback事件处理器存储在ACE_Reactor中的相应的表中。同样地,当调用register_handler方法登记基于I/O的事件处理器时,ACE_Reactor在一个内部表中存储适当的句柄。当应用随后通过调用ACE_Reactor::handle_events方法执行它的主事件循环时,该句柄被作为参数传递给底层的OS I/O多路分离系统调用(例如,select或poll)。

图1-6 ACE Reactor交互图
当与预登记的事件处理器callback对象相关联的输入、输出,和定时器事件在运行时发生时,ACE_Reactor自动检测这些事件并分派适当的事件处理器对象的方法。被分派的callback对象的方法负责完成应用特有的功能(比如写入消息到通信信道、从通信信道读取消息,或是设置触发程序终止的标志)。这些组件间的协作通过对象交互图在图1-6中描述。
ACE并发类属包含的OO包装(例如,ACE_Mutex、ACE_Condition、ACE_Semaphore和ACE_RW_Mutex)封装了相应的Solaris[31]和POSIX Pthreads[32]多线程和同步机制。这些包装使在类中作为成员域出现的同步对象的初始化过程得以自动化,并且还简化了线程和同步机制的常见的使用模式。例如,下面的代码演示了怎样将SunOS mutex_t和cond_t同步机制的ACE封装用于典型的共享资源管理类:
class
Resource_Manager
{
public:
Resource_Manager
(u_int initial_resources)
: resource_add_ (this->lock_),
resources_ (initial_resources) {}
int
acquire_resources (u_int amount_wanted)
{
this->lock_.acquire ();
while (this->resources_ <
amount_wanted)
{
this->waiting_++;
// Block until resources are
released.
this->resource_add_.wait ();
}
this->resources_ -=
amount_wanted;
this->lock_.release ();
}
int
release_resources (u_int amount_released)
{
this->lock_.acquire ();
this->resources_ +=
amount_released;
if (this->waiting_ == 1)
{
this->waiting_ = 0;
this->resource_add_.signal ()
}
else if (this->waiting_ > 1)
{
this->waiting_ = 0;
this->resource_add_.broadcast
();
}
this->lock_.release ();
}
// ...
private:
ACE_Mutex
lock_;
ACE_Condition<ACE_Mutex>
resource_add_;
u_int
resources_;
u_int waiting_;
// ...
};
注意ACE_Condition对象resource_add的构造器是怎样将ACE_Mutex对象lock_与Condition对象绑定在一起的。与底层的SunOS cond_t
cond_wait接口相比较,这样的方式简化了ACE_Condition::wait调用接口。
尽管ACE_Mutex包装为同步多线程控制提供了一种相对优雅的方法,它们仍是潜在地易错的,因为开发者有可能会忘记调用release方法(或是由于程序员的疏忽,或是由于C++异常的发生)。为改善应用的健壮性,ACE同步机制有效地利用了C++类构造器和析构器语义。为确保ACE_Mutex锁被自动获取和释放,ACE提供了名为ACE_Guard的助手类,定义如下:
template
<class MUTEX>
class ACE_Guard
{
public:
ACE_Guard
(MUTEX &m): lock_ (m)
{
this->lock_.acquire ();
}
?ACE_Guard
(void)
{
this->lock_.release ();
}
private:
MUTEX
&lock_;
}
ACE_Guard类的对象定义一个代码块,在块开始时获取ACE_Mutex,而在块结束时自动释放。
注意ACE_Guard类被定义为模板,由互斥机制参数化。有若干不同类型的互斥语义[33]。每种互斥共有一个通用接口(也就是,acquire/release),但具有不同的序列化和性能属性。ACE支持的两种互斥是非递归和递归锁。
template
<class MUTEX>
class
ACE_Recursive_Thread_Mutex
{
public:
// Initialize a
recursive mutex.
ACE_Recursive_Thread_Mutex
(void);
// Implicitly
release a recursive mutex.
?ACE_Recursive_Thread_Mutex
(void);
// Acquire a recursive
mutex.
int acquire
(void) const;
//
Conditionally acquire a recursive mutex.
int tryacquire
(void) const;
// Releases a
recursive mutex.
int release
(void) const;
private:
ACE_Mutex
nesting_mutex_;
ACE_Condition<ACE_Mutex>
mutex_available_;
thread_t
owner_id_;
int
nesting_level_;
};
注意这个类的接口与ACE中可用的其他锁定机制一致[22]。
下面的代码演示怎样将ACE_Guard和ACE_Recursive_Thread_Mutex用于回调机制中:
int Callback::dispatch (const
Event_Handler *eh, Event *event)
{
// Constructor
acquires the lock on entry.
ACE_Guard<ACE_Recursive_Thread_Mutex<ACE_Mutex>
> mon (this->lock_);
eh->handle_event
(event);
// Destructor
of Guard releases the lock on exit.
}