ZeroC ICE的远程调用框架 ThreadPool

ThreadPool提供Reactor/Proactor服务,并且强偶合了Reactor(反应器)/Proactor(前摄器)。不同于Reactor/Proactor使用线程池
进行事件处理的设计。如ACE框架的ACE_TP_Reactor。
同时ThreadPool提供一个共享的工作分派队列,可以用作Half-Async/Half-sync并发模式的线程池。
ThreadPool为池中每个线程定制了一至的线程循环,运行在池中的线程者必须进行这个循环,接受线程池的统一控制。
此外ThreadPool还强偶合了每一个事件处理类,线池程的相关控制也偶合进事件处理程序中去。

ThreadPool组件包含下面的协作类:
ThreadPoolCurrent,是ThreadPool和EventHandlerThread的一个关联
EventHandlerThread,运行在ThreadPool的线程。
Selector,反应器(当不使用IOCP),或前摄器(当使用IOCP)。
ThreadPoolWorkQueue,工作分派队列,优先级别比网络IO事件低。使用ThreadPool进行dispatch的任务都入队到ThreadPoolWorkQueue

EventHandler,定义了可以在线程池执行的事件处理程序的抽象接口。
ThreadPool组件只处理(或者说提供现成的)三种具体的事件:
ThreadPoolWorkQueue,工作队列消费事件。
ConnectionI,网络连接上读写事件,它的事件处理程序强偶合了Ice协议。
IncomingConnectionFactory,被动网络连接事件。
这里要指出ThreadPool还强偶合了上面每一个事件处理类,线池程的相关控制也偶合进事件处理程序"EventHandler::message"中去。
下面还会进一步说明。

相信这时你一定很想听到ThreadPool使用了高性能的Leader/Followers并发模式了。但是要清楚不是使用了LF模式就代表你的软件就高
性能了,LF模式虽然说高性能但也不是什么情况都使用,LF模式是设计用来解决某些模式在某些特定情况的问题的。在Ice的
ThreadPool组件设计中,在使用IOCP前摄器的版本就没有使用LF模式。为什么?因为LF模式不是最佳的选择,在使用IOCP的前摄器没有
LF模式针对解决的问题发生,反而帮倒忙。
ThreadPool组件在使用Reactor(反应器)的版本使用Leader/Followers并发模式,而在Proactor(前摄器)的版本不使用
Leader/Followers并发模式。

ThreadPool组件使用单方法分派接口策略,同一句柄的所有事件类型的事件处理集中在一个单独的事件处理方法。而ACE的设计则是使
用多方法分派接口策略,同一句柄不同事件类型的事件处理分别对应特定的事件处理钩子方法。ThreadPool组件使用单方法分派接口策
略,将具体的事件类型分派交给事件处理方法内进行,而不是由Reactor(反应器)或Proactor(前摄器)最终决定。

EventHandler是一个抽象类,虽然定义了事件处理抽象方法message,却同时也定义事件状态。
事件类型包含Read,Write等,由SocketOperation枚举类型来定义。
EventHandler定义许多由SocketOperation组成的状态。
用于控制的状态:_registered,_ready,_disable。
_registered:一个具体的事件处理类关联到ThreadPool(反应器或前摄器强偶合在ThreadPool)。
_ready:从反应器或前摄器接收事件的逻辑之外,对事件进行激活。
_disable:使用反应器版本的ThreadPool的动态控制。
用于前摄器异步操作状态:_pending,_started,_completed。
_pending:
_started:发起了一次异步I/O操作
_completed:发起了的一次异步I/O操作完成了
此外,EventHandler不保存由反应器或前摄器接收到事件的接收状态,而是在ThreadPool的map容器中,由线程池线程共享。

ThreadPool将事件处理程序分为两阶段,第一阶段是IO,第二阶段是User。在使用Reactor(反应器)版本中,网络指示事件通知了句
柄不再阻塞,可以放心进行同步I/O操作,所以事件处理程序首先要对指示事件进行响应进行同步I/O操作,然后才是事件用于I/O以外
的操作,也就是真正的dispatch。在使用Proactor(前摄器)版本中,事件处理程序并不包含第一阶段,因为异步I/O操作由系统进行
了,并且事件是异步I/O操作的完成事件。ThreadPool统计这两个阶段的事件处理状态的线程数量,用来对线程池整体的控制。
ThreadPool使用_inUseIO对事件处理进行IO阶段的线程统计,_inUse对事件处理进行dipatch阶段的线程统计。
_inUseIO统计数决定是否要进行Leader的推选(promote),只有在反应器版本有效。每次线程以leader身份取出一个事件处理对象,
对相关事件进行同步I/O之前,都要参加统计计数即加1。在完成同步I/O之后,释放统计计数即减1,并根据统计是否要推选。因为反应
器不希望有句柄进行同步I/O的同时而进行阻塞等待句柄的事件。当还有已得事件未处理时,则不用理会这个统计计数而进行推选唤醒
线程处理已得事件。
_inUse统计数决定是否要新增线程,如果正在dispatch的线程数量已经到达了当前ThreadPool拥有的线程数目,还未到达最大限制数目
的时候,必须新增线程。
因为ThreadPool组件在使用LF模式的版本中,引入了_inUseIO统计数来控制竞选的进程(progress,是否允许新一轮的竞选)。并且这
个_inUseIO统计数的功能强偶合进了事件处理类(EventHandler的继承类)的事件处理程序(EventHandler::message)中,事件处理
程序必须对_inUseIO统计数进行释放,如果这个_inUseIO统计数发生了意外,你的LF竞选可能就永远不会进行新一轮的选举,以至候选
线程不可能被唤醒。如果你不明白这一点,去添加自己的事件处理类的话,就可能因没有在事件处理程序中释放这个_inUseIO统计数而
惹上麻烦。虽然Ice在ThreadPool的线程循环里,在事件处理程序执行之后小心地照料了这个_inUseIO统计数,但还是有可能事件处理
程序异常等其它原因,使得你没的程序运行没有按照Ice设计的流程进行,只要有一次_inUseIO统计数没有被正确释放,你的线程池就
会惹上麻烦。
此外,ThreadPool引入的另一个用于线程池控制的统计数_inUse,同样也强偶合在事件处理程序。并且由这个_inUse统计数感知压力而
新增线程的工作也是偶合在事件处理程序。当这个统计数发生错乱后,你可以失去以下便利。尽管你的线程池线程数目没有达到上限,
当前可用线程都用尽了,但也不能新添加线程。而你池中有线程因为异常面退出了,线程池规模缩小,却不能新添加线程补充。

由于ThreadPool引入了上面一些统计数来控制线程池,因此线程池的线程对象EventHandlerThread有下面几种状态:Idle,ForIO,
ForUser等。当线程在上面的状态切换时,都要通知相关的observer。ThreadPool的线程执行的整个流程中,还有许多的事件监视点,
即有许多各类的Observer,这将分开一篇独立写。

ThreadPool提供默认dispatch策略,以及支持自定义dispatcher策略。默认dispatch策略,事件处理执行在当前线程。自定义
dispatcher策略则由用户自定义,并在Ice环境Communicator创建时指定。