杂项

来源:互联网 发布:手机淘宝店招设计 编辑:程序博客网 时间:2024/05/06 21:07
 

20         杂项

这一节列一些ACE使用中要注意的一些问题。

20.1           ACE_Reactor的初始化应尽量提前

由于为了一些自己需要的特性,我一般会自己初始化ACE_Reactor,而不是让系统默认初始化。要注意必须在程序的最开始就初始化ACE_Reactor

由于ACE的很多代码都会使用ACE_Reactor,包括日志的策略类。所以ACE_Reactor必须在这些代码前面,否则会出现奇怪的错误,比如无法响应某些IO,我至少掉到这个陷阱里面5次。

20.2           ACE_SOCK_Stream不会在析构关闭

OO基础的程序都会放资源的释放放入析构中间去。所以我看到ACE_SOCK_Stream也以为他的在析构中关闭Socket的句柄,但是事实是ACE_SOCK_Stream必须自己显式调用close函数关闭Socket句柄。

当然,这倒不是ACE的设计缺陷,而是ACEACE_SOCK_Stream是一个可以出现在堆栈,可以作为参数传递,进行赋值的类,如果在析构中关闭,就无法实现这些功能了。

实现决定设计。辨证呀。

20.3           handle_events函数的ACE_Time_Value参数

Reactorhandle_events参数里面的有一个ACE_Time_Value参数,注意这个参数是一个传入传出参数。

  virtual int handle_events (ACE_Time_Value &max_wait_time);

由于Reactor内部同时要管理定时器和IO句柄,所以ACE很可能不能等待你制定的时间长度,所以他会在传出参数告诉你剩余的等待时间。这时你可以让ACE继续等待剩余时间。但在主循环处理中,你不能这样做,因为经过多次调用后,ACE_Time_Value参数会变成0ACE_Time_Value::zero)。这是会导致hanlde_events空转,会导致CPU占用率很高。

对于大部分主循环的程序,都不需要这样做,而应该重新制定一个等待时间。

20.4           正确理解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函数内部加(解)的锁无法影响后续的调用。

 

20.5           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++的代码还是要执行,在我自己测试中,即使是在GCCO3级别的优化编译中,这样的代码也不会被优化掉。而如果采用ACE_DEBUG的设计,统一替换为do {} while (0),这行代码则必然将被优化掉。而对于MSVC的编译器,他提供一个特别的标识符__noop帮助编译器优化。

 

原创粉丝点击