一、 ACE的链接Link错误
很多人在Windows使用ACE的时候往往会出现以下的Link错误。
\ace/OS.i(2384) : error C2039:
'TryEnterCriticalSection': is not a memberof '`global namespace''
其实这个错误不是由于ACE导致的,只是编译器把这个赃栽倒了ACE上。出现这个错误的原因主要是因为一些关键宏定义冲突,一般是_WIN32_WINNT,'TryEnterCriticalSection' 这个函数是NT4.0后才出现的函数,如果这个宏被定义的小于0x0400或者没有定义,那么就会出现这个错误。
所以最简单的处理方法是在自己的预定义头文件中加入一行。
#if !defined (_WIN32_WINNT)
# define _WIN32_WINNT 0x0400
#endif
其实ACE自己对于宏的处理是比较严谨的,ACE的config-win32-common.h中间就有这行定义,所以在一般而言,可以将ACE的头文件包含定义放在在顶部,这样也可以避免这个编译错误。
二、 Event_Handler在程序退出前应该自己关闭
在程序退出的【注】,我们往往不会自己关闭Event_Handler,而寄希望Reactor 的清理。但是实际情况会复杂很多。使用的时候必须当心。
【注】是否要在退出的时候清理所有分配的内存?在普通的操作系统中,程序的退出会回收所有的分配内存。所以很多人会逃避在最后阶段的清理分配的内存。但是这实在不是一个良好的习惯。一方面对于很多OS(比如嵌入系统)不会回收内存资源,一些内核资源(UNIX)也不会在进程退出后释放,编程就应该要养成清理的好习惯,更何况不进行释放在内存检查的软件一般会报错,如果不清理会干扰我们对于内存泄露的定位。
三、 ACE_SOCK_Stream不会在析构关闭
有OO基础的程序都会放资源的释放放入析构中间去。所以我看到ACE_SOCK_Stream也以为他的在析构中关闭Socket的句柄,但是事实是ACE_SOCK_Stream必须自己显式调用close函数关闭Socket句柄。
当然,这倒不是ACE的设计缺陷,而是ACE的ACE_SOCK_Stream是一个可以出现在堆栈,可以作为参数传递,进行赋值的类,如果在析构中关闭,就无法实现这些功能了。
实现决定设计。辨证呀。
四、 handle_events函数的ACE_Time_Value参数
Reactor的handle_events参数里面的有一个ACE_Time_Value参数,注意这个参数是一个传入传出参数。
virtual int handle_events (ACE_Time_Value&max_wait_time);
由于Reactor内部同时要管理定时器和IO句柄,所以ACE很可能不能等待你制定的时间长度,所以他会在传出参数告诉你剩余的等待时间。这时你可以让ACE继续等待剩余时间。但在主循环处理中,你不能这样做,因为经过多次调用后,ACE_Time_Value参数会变成0(ACE_Time_Value::zero)。这是会导致hanlde_events空转,会导致CPU占用率很高。
对于大部分主循环的程序,都不需要这样做,而应该重新制定一个等待时间。
五、 正确理解ACE_Singleton的加锁
ACE_Singleton的模板参数是可以带一个锁参数的。
template <class TYPE, class ACE_LOCK>
class ACE_Singleton : public ACE_Cleanup
但你可能会错误理解这个锁参数的用途。
typedef ACE_Singleton<Manager,ACE_Thread_Mutex> MANAGER;
MANAGER::instance()->ProcessFunA();
初学者可能会疑惑加锁的是不是ProcessFunA,的处理被加锁了。但是实际上ACE_Singleton的锁只保护ACE_Singleton内部的指针分配和销毁不出现重入。也就是保护instance函数内部的指针分配和释放部分。代码剖析如下:
template <class TYPE, class ACE_LOCK>TYPE *
ACE_Singleton<TYPE,ACE_LOCK>::instance (void)
{
//加锁部分的代码,使用GUARD方式保护new
ACE_GUARD_RETURN (ACE_LOCK, ace_mon, *lock, 0);
if (singleton == 0)
{
ACE_NEW_RETURN (singleton,(ACE_Singleton<TYPE, ACE_LOCK>), 0);
}
……
return &singleton->instance_;
}
其实理解函数栈调用的兄弟应该很容易理解这个问题,ProcessFunA 函数入栈的时候instance函数已经出栈了。instance函数内部加(解)的锁无法影响后续的调用。
六、 ACE_DEBUG的两层括号
这儿只是分析(猜测)一下ACE_DEBUG两层括号的来由。用习惯了Windows下面跟踪宏TRACE的人开始用ACE的调试宏ACE_DEBUG的宏都会有点不习惯,因为你必须写两层括号。
#if defined (ACE_NLOGGING)
#define ACE_DEBUG(X) do {} while (0) /*注意ACE定义的是(X)*/
#else
#define ACE_DEBUG(X) \
do {\
ACE_Log_Msg *ace___ = ACE_Log_Msg::instance (); \
ace___->log X; \ /*注意这儿,这个奇怪的写法*/
}while (0)
#endif
//使用实例,
ACE_DEBUG((LM_ERROR,"i=%d.\n",i++));
比较起来,对于Windows下的TRACE宏的定义如下:
#ifdef _DEBUG
#define TRACE ATLTRACE
#else
#define TRACE __noop /* MSVC特有的一个标识符,用于忽视后面的参数 */
#endif
而ACE_DEBUG的定义比TRACE的定义是多一层(X)的,所以你必须写两层括号,ACE实际上将内层括号的内容全部作为宏参数使用了。
我曾经对这两层括号疑惑了很久。因为我觉得可以采用其他方法绕开两个括号,(你可以写一个日志类尝试一下)
#if defined (ACE_NLOGGING)
// 直接定义为一个函数的名字,当然这儿还要改写其他的很多代码
#define Z_DEBUG ACE_Log_Msg::instance()->log
#else
#define Z_DEBUG
#endif
这样的在没有定义ACE_NLOGGING的时候,Z_DEBUG(LM_ERROR,"i=%d.\n",i++);会被替换成,(LM_ERROR,"i=%d.\n",i++),这样也不会有任何输出效果。
直到有一次发现GCC2.9的环境下编译类似代码,GCC会对这样的代码会产生告警,我大致明白了ACE_DEBUG设计者的苦衷。只有双层括号的方法才能彻底让这行代码不起任何告警。
另外使用两层括号也有性能上的好处,大家注意代码被替换成(LM_ERROR,"i=%d.\n",i++)后,i++的代码还是要执行,在我自己测试中,即使是在GCC的O3级别的优化编译中,这样的代码也不会被优化掉。而如果采用ACE_DEBUG的设计,统一替换为do {} while (0),这行代码则必然将被优化掉。而对于MSVC的编译器,他提供一个特别的标识符__noop帮助编译器优化。
七、 关于在不同线程当中启用run_event_loop()的问题
调用线程必须在调用该函数之前使用ACE_Reactor::instance()->owner(ACE_Thread::self());来获得当前线程的控制权限才能继续的进行事件分发。用TP_Reactor不会有此问题。
八、 停止事件循环的问题
while (bStart)
{
iEvent = ACE_Reactor::instance()->handle_events();
}
我们需要手动给ACE_Reactor::instance()->handle_events();
加一个循环,否则,此函数只进行一次分发就会返回。主意此函数可以设置阻塞时间,如果不设置默认为永远阻塞,所以如果你希望让此函数返回的时候需要从你的ACE_Event_HandlerD队列中移除一个对象比如使用ACE_Reactor::instance()->remove_handler,这样就可以使ACE_Reactor::instance()->handle_events();返回。
九、 考虑不周的Reactor Notify机制
这也应该是一个BUG,Reactor Notify的代码有考虑不周的地方。Notify机制的本质是提供了一条消息队列让大家有方法调用Event_handler,但是存在一种可能,在你的通知消息在消息队列的时候,Event_hanlder由于后面的处理可能已经handle_close了。但是ACE的dispatch_notify却没有考虑倒这一点(或者说考虑倒这一点也不好解决)。
ACE_Select_Reactor_Notify::dispatch_notify函数的代码。
int
ACE_Select_Reactor_Notify::dispatch_notify(ACE_Notification_Buffer &buffer)
{
ACE_Event_Handler *event_handler =
buffer.eh_;
bool const requires_reference_counting =
event_handler->reference_counting_policy ().value () ==
ACE_Event_Handler::Reference_Counting_Policy::ENABLED;
//如果此时这个ACE_Event_Handler已经被handle_close了,也就时删除了此对象,狠容易引起崩溃。
switch (buffer.mask_)
{
case ACE_Event_Handler::READ_MASK:
case ACE_Event_Handler::ACCEPT_MASK:
result = event_handler->handle_input (ACE_INVALID_HANDLE);
这个bug到5.6.1还没有解决。我觉得这个问题是可以解决的,ACE自己再其中可以提供一个删除对象的方法,并在这个方法中进行同步和错误的判断。
而我现在的做法是设置一个TIMER 让ACE去调用汉handle_timeout的时候再来删除所注册的对象。
十、 ACE定时器失效问题
ACEACE_Reactor::instance()->schedule_timer(this, 0, ACE_Time_Value(1)); 如果修改系统时间,比如向前调一段时间,TIMER的触发时按照设置时间的时间点往后加一段时间,所以如果设置1分钟后触发,但此时修改了系统时间向前调了1分钟,这样只有CPU时间的两分钟后才会触发。但如果系统时间向后调,则不会发生影响。
此文章由多篇文章综合拼接而成!