服务器程序框架之4.两种高效的并发模式(1)

新年快乐啊,2018我爱的人和爱我的人,一切顺利开心。服务器程序框架之4.两种高效的并发模式(1)

服务器程序框架---终章 正文:

       并发编程的目的是让程序“同时执行多个任务”。如果程序是计算密集型的,并发编程并没有优势,反而由于任务的切换使效率降低。但如果程序是I/O密集型的,比如经常读写文件,访问数据库等,情况就不同了。由于I/O操作的速度远没有CPU的计算速度快,所以让程序阻塞于I/O于I/O操作将浪费大量的CPU时间。如果程序有多个执行线程,则当前被I/O操作所阻塞的执行线程可主动放弃CPU(或由操作系统来调度),并将执行权转移到其他线程。因此CPU就可以用来做更加有意义的事情,而不是等待I/O操作完成,因此CPU利用率显著提升。

      从实现上来说,并发编程主要有多进程和多线程两种方式。对应图1,并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法。服务器主要有两种并发编程模式:半同步/半异步(half-sync/half-async)模式和领导者/追随者(Leader/Followers)模式。

服务器程序框架之4.两种高效的并发模式(1)图1 并发模式中的同步和异步

       按照同步方式运行的线程称为同步线程,按照异步方式运行的线程成为异步线程。显然异步线程的执行效率高,实时性强,这是很多嵌入式程序采取的模型。但编写以异步方式执行的程序相对复杂,难于调试和扩展,而且不适合于大量的并发。而同步线程则相反,它虽然效率相对较低,实时性较差,但逻辑简单。因此,对于像服务器这种既要求较好的实时性,又要求能同时处理多个客户请求的应用程序,我们就应该使用同步线程和异步线程来实现,即采用半同步/半异步模式来实现。

       半同步/版一部模式中,同步线程处理客户逻辑;异步线程处理I/O事件。异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式的工作线程来读取并处理该请求对象。具体选择哪个工作线程来为新的客户请求服务,取决于请求队列的设计。比如最简单的轮流选取工作线程的Round Robin算法,也可通过条件变量或信号量来随机选择一个工作线程。图2总结了半同步/半异步模式的工作流程。

服务器程序框架之4.两种高效的并发模式(1)

                                                        图 2 半同步/半异步模式的工作流程

在服务器程序中,如果结合考虑两种事件处理模式和几种I/O模型,则半同步/半异步模式就存在多种变体。其中有一种变体称为半同步/半反应堆(half-sync/half-reactive)模式。如图3所示:

服务器程序框架之4.两种高效的并发模式(1)图 3 半同步/半反应堆模式

      图3中,异步线程只有一个,由主线程来充当。它负责监听所有socket上的事件。如果监听socket上有可读事件发生(有新的连接请求道来),主线程就接受新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件。如果连接socket上有读写事件发生(有新的客户请求道来或有数据要发送至客户端),主线程就将该连接socket插入请求队列中。所有工作线程都睡眠在请求队列上,当有任务到来时,它们将通过竞争(比如申请互斥锁)获得任务的接管权。这种竞争机制使得只有空闲的工作线程才有机会来处理新任务。

       图3中,主线程的任务是将就绪的连接socket插入请求队列中,所以图3 所示的半同步/半反应堆模式采用的事件处理模式是Reactor模式:它要求工作线程自己从socket上读取客户请求和往socket写入服务器应答。这就是half_reactive的含义。实际上,半同步/半反应堆模式也可以使用模拟的Proactor事件处理模式,由主线和产能来玩会才能数据的读写。在这种情况下,主线程一般会将应用程序数据、任务类型等信息封装为一个任务对象,然后将其(或者指向该任务对象的一个指针)插入请求队列。工作线程从请求队列中获得任务对象之后,即可直接处理,无需执行读写操作了。

      半同步/半反应堆模式存在如下缺点:

       1)主线程和工作线程共享请求队列。主线程往请求队列中添加任务,或者工作线程从请求队列中取出任务,都需要对请求队列加锁保护,从而耗费CPU事件。

       2)每个工作线程在同一时间只能处理一个任务对象,客户端的相应速度越来越慢。如果通过增加工作线程来解决这一问题,则工作现成的切换也将耗费大量CPU时间。

      图 4描述了一种相对高效的半同步/半异步模式,它的每个工作线程都能同时处理多个客户连接。

服务器程序框架之4.两种高效的并发模式(1)

                                                图 4 高效的半同步/半异步模式

      图4中,主线程只管理监听socket,连接socket由工作线程来管理。当有新的连接到来时,主线程就接受之并将新返回的连接socket派发给某个工作线程,此后该新socket上的任何I/O操作都由被选中的工作线程来处理,直到客户关闭连接。主线程向工作线程派发socket的最简单的方式,是往它和工作线程之间的管道里写数据。工作线程监测到管道上有数据可读时,就分析是否是一个新的客户连接请求的到来。如果是,则把新socket上的读写事件注册到自己的epoll内核事件表中。

       可见,图4中每个线程(主线程和工作线程)都维持自己的事件循环,它们各自独立地监听不同的事件。因此,这种高效的半同步/半异步模式中,每个线程都工作在异步模式,所以它并非严格意义上的半同步/半异步模式。