一切像雾像雨又像风
作者:CppExplore 网址:http://www.cppblog.com/CppExplore/《技术系列综述(一)》介绍了网络层部分。网络层基本都是多路复用函数作为运行的主线程,使用管道或者sockpair与之通讯,这是网络层线程的固有特点,和业务线程的呈现方式完全不同。经过网络层以后,数据开始流向业务线程,现在就顺着数据流向往上看。一 业务线程《技术系列之 线程(一)》有对线程的一个入门描述,里面的消息队列只是示例,真实可用的可以看《技术系列之 线程(二)》。(1)业务线程的划分在业务层面,数据结构仍然是服务器程序的核心。整理出业务需要的数据结构,据此划分线程,保证每个数据结构都在它所属的线程中被修改。如果其它线程想修改该结构,则需要向本线程发送消息,由本线程修改。当然这只是理想情况,有时候处于性能的需要或者和网络层的交互,需要跨线程访问其它线程的数据,此时则需要加锁,这也是线程间交互陷入混乱的一个开端,这种情况要限制在一个很小可控的范围内。(2)带消息队列的线程实现OA/CRM/WorkFlow的系统的关键在于业务的整理、面向对象的设计。而网络服务器不同,服务器的业务相对清晰,相关性强,它设计的关键在于数据模型的整理、线程的划分。可以说线程是服务器的骨架。这里引入几乎每个服务器都会有的一个基础模块:带消息队列的线程类。《技术系列之 线程(二)》实现了一个简单的线程消息队列。在该消息队列基础之上进一步封装,实现带消息队列的线程类。该类的静态类图如下:ThreadQueue参见《技术系列之 线程(二)》。start方法中参数默认为1,大于1则是线程池的实现,里面以线程的方式启动run函数,线程号存入vector,供停止时用。putq方法在其它线程中调用,向该线程发送消息,run方法中循环调用getq获取消息,调用deal_msg处理。实际的业务线程只需要继承该类即可成为带消息队列的线程。(3)监控线程基于上面这个基础模块,可以进一步开发监控线程,监控线程定时向各个线程发送心跳消息,各普通线程收到心跳信息后向监控线程回复,如果某个线程在一定时间内没有回复心跳,则可以采取进一步的修复处理。该方案可以作为系统安全的一个备选方案。以上思想同样适用于嵌入式平台,很多嵌入式平台使用多进程协同处理消息,进程之间使用共享内存或者系统消息队列通讯,思想大同小异,并且同样可以设计监控进程。二 消息映射(1)消息定义以及处理示例有了线程消息队列,也有就有消息传递,接下来就是业务线程获取到消息处理消息。一个业务线程可能要处理多种消息,为区分不同的消息,引入消息类型。如下:
(2)从代码熵引入查表法这里引入“代码熵”的概念,用于描述代码的混乱程度。每千行代码中,出现一个“else”,代码熵加1,出现一个“case”,代码熵也加1。如果1k行代码的熵大于7,我们就认为这个代码已经开始变的混乱了。因此当消息类型不多的时候,使用case是个不错的选择,当处理的消息大于7个并且有扩充趋势的时候,我们就要想另外的办法来代替这种switch...case...的写法。消除else和case最直接的想法是查表法,使用数组下标标记消息类型,数组保存消息处理方法指针。这里不使用map,查询需要o(lgn)不如数组来的直接,另外设计期间就已明确知道消息的类型以及对应的处理函数,不需要动态增减,也不需要使用vector,直接简单的方法就是定义一个static的数组表。(3)消息映射直接的数组展现方式不携带语义,展现方式不直观,维护和扩充都让人头大。这里可用借助宏包裹数组,提供可读的展现方式,如下:
如果你熟悉MFC,一定很熟悉这种消息映射的定义方式。这种消息映射的定义方式,从可维护、可读方面比直接的数组更进了一步。宏BEGIN_MESSAGE_MAP、ON_MESSAGE的实现方式不再详写,如果读者实在想象不出来,可以参见《技术系列之 状态机(一)》中的状态机映射宏的定义方式。使用的时候在deal_msg中直接根据消息类型找到数组中的消息处理函数进行处理,如果你认为这样暴露了消息映射背后的数组结构,可以把这个寻找消息处理函数的工作也封装到基类IMsgThread中。(4)成员函数委托上面的消息映射宏展开后实际是一个静态数组,而方法do_msg_type_1_/do_msg_type_2_也必须是类的静态成员函数(普通类成员函数指针不能转化为普通函数指针)。通过类的静态成员函数访问类的非静态属性或者方法如下:在消息中携带该类指针handler,处理方法中取到handler指针转换类型,通过指针操作。当代码中充斥大量通过静态成员函数访问对象私有属性的时候,这无疑是一种丑陋的写法(事实并没有这么严重)。这里就该boost::function,boost::bind出场了。如果你喜欢,也可以直接写模版实现。也可以参见csdn文章《成员函数指针与高性能的C++委托》(5)题外话1、统一的展现方式。不仅变量的命名需要统一规范,方法的调用逻辑同样需要统一,这可以帮助你明确程序中数据的流向以及保证程序持续的扩充、维护。相对于简单的命名规范,方法调用逻辑的统一更为重要。以线程类举例说2点(1)其它线程类不能直接调用其它线程的putq方法向对应线程发送消息。正确的做法是调用对应线程类的方法,由该方法负责向本线程发送消息。(2)发送消息的方法/处理消息的方法职责要明确、命名要统一。发送消息的方法负责把方法参数转化为消息内容,调用putq发送消息,该方法不得操作本类的任何私有属性。处理消息的方法负责对消息做出处理、响应。命名方面,比如发送消息的方法可以以ON_开头,处理消息的方法可以以DO_开头。这些规范不应该只是规范,而应该是发自内心的需要。当然没有什么规范是必须的,你仍然可以使用你喜欢的或者认为可行的方式,如果你的方法在程序1w行、10w行、50w行的时候,仍能清晰表现程序的数据流向,仍有很好的可维护性、可扩充性。2、开发领域的烙印。不多说了。一句话:重要的是思想,不是平台和语言。
Powered by: C++博客 Copyright © cppexplore