1、并发编程与并发模式react
并发编程主要是为了让程序同时执行多个任务,并发编程对计算精密型没有优点,反而因为任务的切换使得效率变低。若是程序是IO精密型的,则因为IO操做远没有CPU的计算速度快,因此让程序阻塞于IO操做将浪费大量的CPU时间。若是程序有多个线程,则当前被IO操做阻塞的线程可主动放弃CPU,将执行权转给其它线程。(*IO精密型和cpu精密型能够参考此文:CPU-bound(计算密集型) 和I/O bound(I/O密集型))linux
并发编程主要有多线程和多进程,这里咱们先讨论并发模式,并发模式指:IO处理单元和多个逻辑直接协调完成任务的方法。服务器主要有两种并发编程模式:编程
2、半同步/半异步模式(half-sync/half-async)服务器
这里的“同步”和“异步”和“IO”的“同步”“异步”是彻底不一样的概念。在IO模型中,“同步”和“异步”区分的是内核向应用程序通知的是何种IO事件(是就绪事件仍是完成事件),以及该由谁来完成IO读写(是应用程序仍是内核)。在并发模式中,“同步”指的是程序彻底按照代码序列的顺序执行;“异步”指的是程序的执行须要由系统事件来驱动。常见的系统事件包括中断、信号等。多线程
下图1描述了并发模式同步读操做(图1a)和异步读操做(图1b)并发
图1并发模式同步读(a)和异步读(b)异步
已同步方式运行的线程为同步线程,异步方式运行的为异步线性,异步线程的执行效率高,实时性强,但编写异步方式执行的程序相对复杂,难于调试和扩展,并且不适合于大量的并发。同步线程则相反,它虽然效率相对较低,实时性较差,但逻辑简单。socket
所以对应服务器要求实时性及同时处理多个请求的程序,能够同时使用同步线程和异步线程即采用半同步/半异步模式。同步线程用于处理客户逻辑,异步线程用于处理IO事件。异步线程监听到客户请求后,就将其封装成请求对象并插入到请求队列中。请求队列将通知某个工做在同步模式的工做线程来读取并处理该请求对象。具体哪一个线性处理取决于请求队列的设计。下图2为半同步/半异步的工做流程async
图2半同步/半异步的工做流程函数
在半同步/半异步模式能够变体成为半同步/半反应堆(half-sync/half-reactive),以下图3
图3半同步/半反应堆模式
半同步/半反应堆中,异步线程只有一个,即主线程,他负责监听全部事件,有事件发生则将事件插入请求队列中。工做线程休眠在请求队列中,当任务到来时,经过竞争获取任务处理权
在上图3半同步/半反应堆中,主线程插入工做队列的为就绪的链接socket,他要求工做线程本身socket读取数据和往socket写入服务器应答,全部能够看做Reactor模式。实际也能够模拟为Proactor模式,即主线程完成数据的读写,将数据封装成任务对象插入请求队列,工做线程从请求队列取出任务对象处理。(Reactor模式和Reactor模式能够参考此文:服务器两种高效的事件处理模式)
半同步半反应堆模式存在以下缺点:
一、主线程和工做线程共享请求队列,对请求队列的操做需求加锁,耗费CPU时间。
二、每个工做线程在同一时间只能处理一个客户请求。客户数量多,工做线程少,请求队列任务堆积,响应满,若是添加试图经过增长线程则,因为线程切换致使的CPU时间消耗。
这里咱们再介绍一种高效的半同步/半异步模式:每一个工做线程都能同时处理多个客户链接。
图4 高效半同步/半异步模式
主线程只管理监听socket,链接socket由工做线程来管理。当有新的链接到来时,主线程就接受之并将新返回的链接socket派发给某个工做线程,此后该socket上的任何IO操做都由被选中的工做线程来处理,直到客户端关闭链接。主线程向工做线程派发socket的最简单的方式,是往它和工做线程之间的管道里写数据。工做线程检测到管道里有数据可读时,就分析是不是一个新的客户链接请求到来。若是是,则把该新socket上的读写事件注册到本身的epoll内核事件表中。
每一个线程(主线程和工做线程)都维持本身的事件循环,它们各自独立的监听不一样的事件。所以在这种模式中,每一个线程都工做在异步模式,因此它并不是严格意义上的半同步半异步模式。
3、领导者/追随者模式(Leader/Followers)
领导者/追随者模式是多个工做线程轮流得到事件源集合,轮流监听、分发并处理事件的一种模式。在任意时间点,程序都仅有一个领导者线程,它负责监听IO事件。而其余线程都是追随者,它们休眠在线程池中等待成为新的领导者。当前的领导者若是检测到IO事件,首先要从线程池中推选出新的领导者线程,而后处理IO事件。此时,新的领导者等待新的IO事件,而原来的领导者则处理IO事件,两者实现了并发。
包含以下几个组件:
关系以下图5
图5 领导者/追随者模式的组件
一、句柄集
句柄表示IO资源,linux下一般是文件描述符。句柄集使用wait_for_event方法监听这些句柄上的IO事件,并将其中的就绪事件通知给领导者线程。领导者调用绑定到Handle上的事件处理器来处理事件。绑定是经过句柄集的register_handle方法实现的。
二、线程集
全部工做线程的管理者,负责线程同步、推选新领导。线程在任一时间必处于如下三种状态之一:
这三种状态之间的转换关系图以下图6:
图6 领导者/追随者模式的状态转移
注意,领导者推选新领导和追随者等待成为新领导这两个操做都会修改线程集,所以线程集提供一个Synchronizer来同步。
三、事件处理器和具体的事件处理器
事件处理器一般包含一个或多个回调函数handle_event。这些回调函数用于处理事件对应的业务逻辑。事件处理器在使用前须要被绑定到某个句柄上,当该句柄有事件发生时,领导者就执行绑定的事件处理器的回调函数。具体的事件处理器是事件处理器的派生类。它们从新实现基类的handle_event方法,以处理特定的任务。
因为领导者本身监听IO事件并处理客户请求,该模式不须要在线程间传递额外数据,也无需像半同步/半反应堆模式那样在线程间同步对请求队列的访问。可是,该模式的明显缺点是仅支持一个事件源集合,所以也没法让每一个工做线程独立管理多个客户链接。
咱们将领导者/追随者模式的工做流程总结以下图7
图7 领导者/追随者模式的工做流程
注(本文内容参考 Linux高性能服务器编程——第八章 游双著)