半同步/半异步模式:
首先要区别于I/O模型中的同步异步:
I/O模型中: 同步和异步是指内核向应用程序通知的是何种I/O事件(就绪事件/完成事件),以及该由谁来完成I/O读写(应用程序/内核);I/O模型中的同步和异步是用来形容I/O事件的完成。
半同步与半异步模式中: 同步主要是指程序完全按照代码序列的顺序执行;异步是指程序的执行需要由系统事件来驱动(常见的系统事件:中断,信号等)。 半同步半异步中的同步和异步主要指的是程序的执行顺序。
并发模式中的同步和异步(以读事件为例):
同步线程的执行为按照顺序每次只能有一个线程请求资源,直到资源请求成功才能结束这个进程。效率较低,但设计简单。
异步线程(以信号为例)当一个线程申请资源时,线程将需求传送给信号处理函数,信号处理函数在完成线程的需求后将线程所需的资源返回个线程。
同步线程的运行虽然效率相对较低,实时性较差,但是逻辑简单。异步线程的执行效率高,实时性强,这是很多嵌入式程序采用的模型,但异步方式执行的线程相对复杂,难于调试和扩展。所以对于服务器这种既要求较好的实时性,有要求能同时处理多个客户请求得应用程序,可以同时使用同步线程和异步线程来实现,即就是半同步/半异步模式来实现。
半同步/半异步模式
同步线程用于处理客户逻辑(下图的逻辑单元);异步线程用于处理I/O事件(下图的I/O处理单元)。异步线程监听到客户的请求后,就将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式下的工作线程来读取并处理请求对象。(具体的如何选择工作线程取决于请求队列的设计。)
首先先了解以下服务器基本框架:
具体的半同步/半异步工作流程如下:
结合考虑两种事件处理模式和集中I/O模型,半同步/半异步模式就存在多种变体
下面介绍几种较为经典的模式:
半同步/半反应堆模式:
异步线程只有一个,由主线程来充当,负责监听所有socket上的事件,如果所监听的socket上有事件发生,即新的连接请求到来,主线程接受得到新的来凝结socket,然后往epoll内核事件表中注册该socket上的读写事件。连接的socket上有读写事件发生,即有新的客户请求到来或有数据要发送到客户端,主线程就将该连接socket插入请求队列中,当有任务到来时,工作线程通过竞争(如申请互斥锁)获得任务的接管权,这种竞争是的只有空闲的工作线程才有机会来处理新任务,这样就可以合理的分配任务。(该模式采用的事件处理模式为Reactor模式)。
缺点:
- 主线程和工作线程共享请求队列。主线程王请求队列中添加任务,或者工作线程从请求队列中取出任务,都需要请求队列加锁保护,从而耗费CPU时间。
- 每个工作线程在同一时间只能处理一个客户请求,客户数量较多,工作线程骄傲少,则请求队列将会堆积很多任务对象,客户端响应速度越来越慢。如果增加线程来解决,工作线程的切换也会耗费大量CPU的时间。
领导者/追随者模式:
领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听,分发并处理事件的一种模式。在任意时间点,程序只有一个领导者线程,它负责监听I/O事件。而其他线程都是追随者,他们休眠在线程池中等待成为新的领导者。当前的领导者如果检测到I/O事件,首现要从线程池中推选出新的领导者,然后处理I/O事件。此时,新的领导者等待接新的I/O事件,而原来的领导者则处理I/O事件,二者实现了并发。
领导者/追随者模式组件:句柄集,线程集,事件处理器和具体事件处理器。
句柄集:
句柄用于表示I/O资源,在Linux下通常就是一个文件描述符。句柄集管理众多句柄,他使用wanit_for_event方法来监听这些句柄上的I/O事件,并将其中的就绪事件通知给领导者线程。领导者则调用绑定到Handle上的事件处理器来处理事件,领导者将Handle和事件处理器绑定是通过调用句柄集中的register_handle方法实现的。
线程集:
线程集是所有工作线程(包括领导者线程和追随者线程)的管理者。负责各线程之间的同步,以及新领导者线程的推选。线程集中的线程子在任一时间必处于如下三种状态之一:
(1)Leader:线程当前处于领导者身份,负责等待句柄集上的I/O事件。
(2)Processing:线程正在处理事件。领导者检测到I/O事件后,可以转移到Processing状态来处理事件,并调用promote_new_leader方法推选新的领导者;也可以指定其他追随者来处理事件,此时领导者的地位不变。当处于Processing状态的线程处理完事件之后,如果当前线程集中没有领导者,则它将会成为新的领导者,否则它就直接转变为追随者。
Follower:线程当前处于追随者身份,通过调用线程集的join方法等待成为新的领导者,也可能被当前的领导者指定来处理新的任务。
状态转移图:
领导者线程推选新的领导者和追随者等到成为新的领导者者两个操作都将修改线程集,因此线程集提供一个成员Synchronizer来同步这两个操作,以避免竞态条件。
事件处理器和具体时事件处理器:
事件处理器通常包含一个或多个回调函数handle_event。这些回调函数用于处理事件对应的业务逻辑,事件处理器在使用前需要被绑定到某个句柄上,当该句柄上有事件发生时,领导者就执行与之绑定的时间处理器的回调函数。具体的事件处理器是事件处理器的派生类。它们必须重新实现积累的handle_evevnt方法,以处理特定的任务。
领导者/追随者模式工作流程图:
领导者/追随者模式仅支持一个事件源集合,因此无法让每个工作线程独立的管理多个客户连接。
参考自《Linux高性能服务器编程》