-
半同步/半异步模式
半同步/半异步模式中的同步和异步和前面的IO模型中的同步和异步是完全不用的概念。在IO模型中,同步和异步区分的是内核向应用程序通知的是何种IO事件(是就绪事件还是完成事件),以及该由谁来完成IO读写(是应用程序还是内核)。在并发模式中,同步指的是程序完全按照代码序列的顺序执行,异步指的是程序的执行需要由系统事件来驱动。常见的系统事件包括中断 信号等。
按照同步方式运行的线程称为同步线程,按照异步方式运行的线程成为异步线程。显然异步线程的执行效率高,实时性强,这是很多嵌入式程序采用的模型。但编写异步方式执行的程序相对复杂,难于调试和扩展,且不适合大量的并发。而同步线程则相反,它虽然效率比较低,实时性较差,但逻辑简单。因此,对于像服务器这种既要求较好的实时性,又要求同时处理多个客户请求的应用程序,我们就应该同时使用同步线程和异步线程来实现。即使用半同步/半异步模式来实现!
半同步/半异步模式中,同步线程用于处理客户逻辑,异步线程用于处理IO事件。异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式的工作线程来读取并处理该对象。具体选择哪个工作线程来为新的客户请求服务,则取决于请求队列的设计。
半同步/半反应堆模式
异步线程只有一个,由主线程充当。它负责监听所有socket上的事件。如果监听socket上有可读事件发生,即有新的连接请求到来,主线程就接受得到新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件。如果连接socket上有读写事件发生,主线程就将该连接socket插入请求队列。所有工作线程都睡眠在请求队列上,当有任务到来,通过竞争获得任务管理权。
主线程插入请求队列的是连接socket,说明采用的事件处理模式是Reactor模式;即要求工作线程自己从socket上读取客户请求和往socket写入服务器应答。半同步/半反应堆模式也可以用模拟的Proactor事件处理模式(用同步I/O模拟出Proactor模式的一种方法):此时,需要主线程完成数据的读写,主线程一般将应用程序数据,任务类型等信息封装成一个任务对象,然后插入请求队列;工作线程从请求队列中取得任务对象之后,就可直接处理,不再需要进行读写操作。
缺点:
主线程和工作线程共享请求队列。主线程向请求队列添加任务,工作线程从请求队列取出任务,都需要对请求队列加锁保护,从而耗费了CPU时间。
每个工作线程在同一时间只能处理一个客户请求。如果客户数量多,工作线程少,则请求队列中就会堆积很多任务,客户端的响应速度越来越慢。如果增加工作线程,则工作线程切换也将耗费大量CPU时间。
高效的半同步/半异步模式
主线程只管理监听socket、连接,得到新的连接socket由工作线程来管理。当有新的连接到来时,主线程就接受并将新返回的连接socket派发给某个工作线程,此后,该socket上任何I/O操作都由被选中的工作线程来处理,知道客户端关闭连接。主线程向工作线程派发socket的方式,是往它和工作线程之间的管道里写数据。工作线程检测到管道上有数据可读时,就分析是否是一个新的客户端连接请求到来,如果是,就把该socket上的读写事件注册到自己的epoll内核事件表中。此模式每个线程都维持自己的事件循环,各自监听不同的事件。
-
领导者/追随者模式
该模式是多个线程轮流获得事件源集合,轮流监听、分发并处理事件,在任意时刻,程序仅有一个领导者线程,负责监听I/O事件;其他线程都是追随者,休眠在线程池中等待成为新的领导者或被指定处理任务。当前领导者如果检测到I/O事件,首先要从线程池中推选出新的领导者线程,然后处理I/O事件;新的领导者则等待新的I/O事件。领导者/追随者模式包含的组件:句柄集(HandleSet)线程集(ThreadSet)事件处理器(EventHandler)具体的事件处理器(ConcreteEventHandler)
句柄集:用于表示I/O资源,在Linux下通常就是一个文件描述符。句柄集管理众多句柄,它使用wait_for_event方法监听这些句柄上的I/O事件,并将其中的就绪事件通知给领导者线程。领导者则调用绑定到Handle上的事件处理器来处理事件。领导者将Handle和事件处理器绑定是同通过调用句柄集中的register_handle方法是实现的。
线程集:这个组件是所有工作线程(包括领导者线程和追随者线程)的管理者。负责各线程之间的同步以及新的领导者线程的推选。线程集中的线程在任意时间必处于三种状态之一:
1.Leader:线程当前处于领导者身份,负责等待监听句柄集上的I/O事件。
2.Processing:线程正在处理事件,领导者检测到I/O事件之后,可以转移到Processing状态来处理该事件,并调用promote_new_leader方法推选新的领导者;也可以指定其他追随者来处理时间(Event Handoff),此时领导者的地位不变;当处于Processing状态的线程处理完事件之后,如果当前线程没有领导者,则它将成为新的领导者,否则它将直接转变为追随者。
领导者线程推选新的领导者和追随者等待成为新的领导者这两个操作都将修改线程集,因此,线程集提供一个成员Synchronizer来同步这两个操作,避免竞态条件。
事件处理器和具体的事件处理器:事件处理器通常包含一个或多个回调函数handle_event。这些回调函数用于处理事件对应的业务逻辑。事件处理器在使用前需要绑定到某个句柄上,当该句柄上有事件发生时,领导者就执行与之绑定的事件处理器中的回调函数。具体的事件处理器:是事件处理器的派生类。它们必须重新实现基类的handle_event方法,以处理特定的任务。
缺点:因为领导者自己监听I/O事件并处理客户请求,因此不需要在线程之间传递任何额外的数据,也无须像半同步/半反应堆模式那样在线程之间同步对请求队列的访问。但领导者/追随者仅支持一个事件源集合,因此无法像半同步/半反应堆模式那样让每个工作线程独立管理多个客户连接。
本文内容参考 Linux高性能服务器编程——第八章 游双著