100402关于《0bug-C/C++商用工程之道》的读者来信回复

这是发到我邮箱的一封读者来信,对我的书《0bug-C/C++商用工程之道》提出了一些疑问,首先,我非常感谢这位读者细心的阅读,以及对我书中一些错误的指出,非常感谢,下面我来一一回答。
原文如下:
您好,肖舸老师:
    我看了您写的《c++ 商用之道》很受启发,学到了很多并行编程的知识,以往都是使用别人现成的库编写一些小程序,没有认真认识其中的原理,实际上我只是在业余开发些程序,用到的知识都是网上学来的,写的程序也是七拼八凑的,直到看到您的书才了解了商用开发的实战用知识,对我的确帮助很大.
    我认真看了您书中的每一个部分,有些部分看了很多遍,并且我将书中的代码全部录入调试,得到了一个很好用的实用库。
    但在使用中也现了几个问题:
    1、内存注册机制很好,但使用链表效率偏低,如果使用MAP效果将会更好(建议在调试时开启使用内存注册机制,一旦程序稳定,就将其关闭)。
    2、内存的管理,防止了内存碎片,但也增加了内存分配时的开销,在内存分配频繁的情况下其占用的开销不容忽视。
    3、线程池采用轮询的方式,检查有无新任务,使系统在无任务时CPU使用率也偏高。
    4、CTonyXiaoMemoryQueue链表类,设计的非常好用,但存在一个可能的BUG,就是在m_pLast加速指针的使用上有点问题。
    书中391页中函数CTonyXiaoMemoryQueue::DeleteATokenAndHisNext中m_pLast在函数的最后被设置为NULL,但DeleteATokenAndHisNext又被函数DeleteFirst频繁使用,导至m_pLast总是被设置为NULL,实际上没有起到加速的作用,建议DeleteFirst不要调用DeleteATokenAndHisNext函数,而是直接操作链表。
    5、CTonyXiaoMemoryQueue中使用了递归,如果有人想将该队列的容量增加,将会发生栈溢出错误。
    还有就是想请教肖舸老师,如果我在处理高负荷的任务,要启用多个线程,并且将大量任务分配到不同的工作线程,要如何做才能使任务不积压,我发现在高负荷的情况下,资源的争用反而成为系统负荷的一部分.要如何才能解决这些问题呢?
    谢谢.
我的回答:
 
这位读者你好,首先非常感谢你对我的书的关注和爱护,你能把书中的工程库这么快就输入一遍,我很佩服你的耐心和学习的毅力,这里我先回答你的问题,同时,我也很愿意和你交个朋友,以后多多沟通。说不定,你也有很多东东值得我学习的。呵呵。
首先,关于内存注册机制的效率问题,这确实是一个很大的问题。因为我的工程库是有传承性的,最开始实现出来的就是内存池,我后面的哈希被动池都是在内存池的支撑下工作,因此,在内存池这个层面,还没有考虑使用哈希map机制。这导致注册机制效率很低。
我通常的解决方案是内存池内部设置了注册机制关闭开关,就是内存池构造函数的第二个参数:
Code:
  1. CTonyMemoryPoolWithLock(CTonyLowDebug* pDebug,bool bOpenRegisterFlag=true);  
只要把第二个参数设置为false,则内部自动关闭注册,外部程序无需做任何修改,此时,内存池可以获得最高速度的效率。我工作时,通常开发和测试期打开这个注册开关,供检查bug使用,而到了产品发布时,会关闭,因为前期已经检查过了,没有内存泄露,发布时关闭,可以获得最高的效率。
当然,如果你愿意,完全可以使用C++基本库的map类来做个注册映射,这会直接提升很多效率,我也很欢迎你这么做,甚至,如果有可能,我希望你能把你改版代码公布出来,我也学习一下。
我把源代码详细地注释公开,其实就是鼓励读者不要被我的代码局限住,我认为,只要学会了书中的思想,读者完全可以创造属于自己的工程库。
关于内存池效率问题,这个我也考虑过,我在关闭了注册机制后,在一台赛扬1.4G的老笔记本上实测效率,内存池每秒钟可以支撑的申请或释放动作,达到每秒40万~50万次,至少在我过去经过的商用服务器工程项目,看起来这个速度是够了,我建议可以先不忙考虑算法效率问题,先用吧,当然,也欢迎你对这个问题提出你的优化方案。
关于线程池效率问题,我建议你再看看第九章,里面我专门针对线程池服务线程,里面的Sleep做了优化建议,很多时候,这个线程池不是固定的,都是根据具体工程项目做针对性优化,因此,我书中代码本身也不是一成不变的,可以根据我的优化建议来优化,你也可以根据自己实际工程需要,提出自己的优化见解。这点,如果你不确定,可以给我发信,我可以帮你思考一下这个效率优化方案。
Code:
  1. void CTonyXiaoTaskPool::TaskServiceThread(void* pCallParam, //这是主程序传进来的参数指针   
  2.                               MBOOL& bThreadContinue)       //如果系统要退出,线程池会修改这个参数的值为false   
  3. {   
  4.     //...   
  5.     while(XMG(bThreadContinue))   
  6.     {   
  7.         if(!pThis->m_bThreadContinue.Get())    
  8.         {   
  9.             goto CTonyXiaoTaskPool_TaskServiceThread_End_Process;   
  10.         }   
  11.         nQueueRet=pThis->m_pTaskQueue->GetAndDeleteFirst(szTask,STaskPoolTokenSize);   
  12.         if(STaskPoolTokenSize==nQueueRet)   
  13.         {   
  14.             pThis->TaskServiceThreadDoIt(Task);   
  15.         }   
  16.         Sleep(MIN_SLEEP); //请务必关注此处,在第九章有专门一节描述如何优化这个点的技巧。   
  17.     }   
  18. CTonyXiaoTaskPool_TaskServiceThread_End_Process:   
  19.     pThis->m_nThreadCount.Dec();   
  20.     //pThis->XGSyslog("CTonyXiaoTaskPool::TaskServiceThread(): %d Stop!\n",nID);   
  21. }  
 
 
关于第四个问题,队列的隐含bug问题,我也查了一下代码。这个真得感谢你了,你把我代码中一个潜在的bug找出来了。十分感谢。
我查了一下,CTonyXiaoMemoryQueue::DeleteATokenAndHisNext这个函数,只有两个地方被调用,一个是你信中所说的CTonyXiaoMemoryQueue::DeleteFirst
Code:
  1. bool CTonyXiaoMemoryQueue::DeleteFirst(void)   
  2. {   
  3.     bool bRet=false;   
  4.     STonyXiaoQueueTokenHead* pSecond=null;   
  5.     if(!ICanWork()) goto CTonyXiaoMemoryQueue_DeleteFirst_End_Process;   
  6.     if(!m_pHead) goto CTonyXiaoMemoryQueue_DeleteFirst_End_Process;   
  7.     pSecond=m_pHead->m_pNext;   
  8.     m_pHead->m_pNext=null;   
  9.     bRet=DeleteATokenAndHisNext(m_pHead);  //此处被调用   
  10.     if(bRet)   
  11.     {   
  12.         m_pHead=pSecond;   
  13.         if(!m_pHead)   
  14.             m_pLast=null;   
  15.     }   
  16.     else  
  17.     {   
  18.         TONY_DEBUG("%s::DeleteFirst(): delete m_pHead fail!\n",   
  19.             m_szAppName);   
  20.         m_pHead->m_pNext=pSecond;   //删除失败,还得恢复回去   
  21.     }   
  22. CTonyXiaoMemoryQueue_DeleteFirst_End_Process:   
  23.     //m_pLast=null;
  24.     return bRet;   
  25. }  
一个是队列类的析构函数:
Code:
  1. CTonyXiaoMemoryQueue::~CTonyXiaoMemoryQueue()   
  2. {   
  3.     if(m_pHead)   
  4.     {   
  5.         DeleteATokenAndHisNext(m_pHead); //此处被调用   
  6.         m_pHead=null;   
  7.         m_pLast=null;   //增加这一句,虽然有点可有可无,但是建议增加   
  8.     }   
  9. }  
很显然,m_pLast=null这句话,其实仅仅是析构函数的逻辑语义才需要,如我上面这个例子,而DeleteFirst里面,是删除第一个元素m_pHead,且有如下字样:
Code:
  1. m_pHead->m_pNext=null;      
  2. bRet=DeleteATokenAndHisNext(m_pHead);  //此处被调用      
也就是说,这里调用这个删除函数,仅仅删除一个元素,全队列的第一个函数,确实不应该去修改m_pLast,这属于设计时的思路混乱,导致队列性能严重受影响,因此,我建议你直接将m_pLast=null;这句话通过注释隐藏,因为没有必要。这会大幅度提升队列的工作效率。
至于第五点,我书中本意是不建议递归的,但是,在队列中我使用了递归,书里面我是有个解释的。主要是我利用这个队列,并不主要用于存储,而是用于高速的信令转发,即队列两边通常都是高速线程在不断插入数据和弹出数据,做异步化使用,因此,队列长度在我的使用模型里面,总是很短的,这个递归效果不危险,也就没有再做过多设计,直接使用了递归方式。
这是我常见的业务模型决定的,但是,如果以后读者要使用这个队列来做大规模数据存储,确实如你所说,需要修改一下递归策略,最好改成循环,我本人也建议这么做。
这里补充一点,在出版本书时,我一直没有公布源代码,主要问题就是我觉得,书中的工程库,虽然有一定实用性,但是,它是我在这么多年工作中总结和产生的,不可避免带着我很多工作的痕迹,并不是放之四海皆真理的东东,对于其他读者,有指导意义,但不建议拿来就用,所以我不公开源代码。
其实从你这几个问题,就已经体现出这一点了,因此,我建议《0bug-C/C++商用工程之道》的读者,最好不要钻进代码里面,更多地,请参考书中写出来的思想,然后利用这个思想,来创造自己的工程库,这才是客观务实的态度。希望各位读者能深刻理解这一点。
还是那句话,代码不值钱的,值钱的是思想。
最后,还是感谢你的关注,很高兴和你交个朋友,希望我们能有机会继续探讨技术。
肖舸
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值