开源软件实践之linux高性能服务器编程框架和选型(续)
接着昨天的Proactor模式介绍。
proactor模式将所有IO操作交给主线程和linux内核来处理,工作线程负责业务逻辑。异步IO实现这种模式的工作流程如下:
1)主线程使用aio_read函数注册socket读完成事件,提供用户接收数据的缓冲区地址以及读操作完成时如何通知应用程序(有信号等手段);
2)主线程继续处理其他逻辑;
3)当socket数据读入缓冲区后,内核通知应用程序(如发送一个信号);
4)应用程序通过信号处理函数选择一个工作线程进行业务逻辑处理,处理完成之后调用aio_write注册写完成事件,提供写的缓冲区地址以及通知应用程序的手段;
5)主线程继续处理其他逻辑;
6)写数据缓冲区接收完成数据以后,同样通知应用程序,然后应用程序选择一个工作线程处理后面的业务逻辑。
异步数据的读写具体请参考aio_read和aio_write函数,这种模式很类似windows的完成端口网络编程模型。
当然可以通过同步的手段模拟proactor模式,具体流程大家可以自己想想。
说完了事件处理模式,我们在继续看并发模式,并发的概念可以自己查询相关资料。
1.半同步/半异步
说明:这里同步和异步不同于读写函数同步和异步,这里的同步和异步主要指定是代码的执行过程和流程,同步大家都知道按照代码的逻辑执行,异步通常是通过事件或者信号等触发然后调用回调函数进行的处理,所以代码的执行流程未知,可能随时被信号或者中断等改变。
同步代码的优点:编程简单; 缺点:效率相对较低,实时性差;
异步代码的优点:执行效率高,实时行比较强; 缺点:编程复杂,难于调试和扩展。
所以半同步/半异步模式结合各自的优点,这种模式的逻辑主要是通过主线程来接收客户端的连接请求,然后把建立的连接请求和相应的事件丢给工作线程,具体由哪一个工作线程来处理,这个可以有很多中算法,根据业务场景选择最合适的,例如有RR(轮询),随机,或者是否空闲等,当然在主线程和工作线程之间可以加入任务队列,空闲线程主动去取任务进行处理;
上面这种模式,主线程复杂注册和读取所有socket的读写事件,然后根据事件类型做任务分发,当然工作线程也可以读写分别使用不同的线程处理,但是这样资源不能合理利用,如果有这种需求是可以考虑的。
首先考虑一下这种模式的如下逻辑处理:主线程负责所有socket事件的监听和读取,有对应的读写数据事件了就统一放入一个队列中,然后各个工作线程就在这个队列中取任务,这种模式好处就是保证空闲的线程首先得到任务处理,资源合理利用,但是这个时候一个线程只能同时处理一个socket,也就是一个客户端,而且这个任务队列所有线程都会操作必然需要加锁,这样极大的降低了并发。
那么我们在这种模式改进或者叫做变通一下,就是主线程之负责处理监听socket的事件,也就是只负责接收连接,然后新建立的连接socket(和客户端传输数据的socket)丢给工作队列自己的注册事件和读取事件,这样并发数就提高了,这种模式就很高效了。
2.领导者/追随者
简单说一下这种模式的工作原理吧,这种模式所有的工作线程都是等同的,但是在不同时刻状态是可以发生改变的:即某一个时刻只有一个工作线程成为领导者线程,其他线程成为跟随线程,那么领导者线程做什么工作?跟随线程又做什么工作?下面分析:
领导者线程既然是领导者那么就要带头做实事涩,不像真正的"领导"只指挥别人做事,这里领导就是干事实的。领导者线程负责监听IO事件,一旦接收到IO事件(epoll模型同时可以接收多个事件),它就应该去处理这些事件了。但是不慌,你去做事了,应该先推选出下一个领导接替你的位置,那么在你做事期间被你推选的领导可以继续监听IO事件,这样就实现了轮流当领导而且当领导就必须的干事实,多么和谐呀。而且流水线的,并发也实现,工作绩效也就提高了嘛,而不是一位的等待别人给你分配任务。
关于并发就说这么多吧,下面说说我的开源软件目前使用的逻辑:一个主线程负责监听所有事件,然后把监听到的事件丢给一些固定的线程处理,后面准备改成主线程只负责监听客户端连接的事件,其他客户端的读写请求事件由工作线程自己注册监听和处理。
不过后面可以研究一下nginx采用的是哪一种方式。
其他提高服务器性能的建议和方案:(1)利用各种池,例如线程池,内存池等;(2)减少数据复制;(3)减少上下文切换的开销;(4)降低锁的粒度或者提倡无锁编程。
预告:后面一遍博客主要讲解一下怎么解析http协议,应该是高效的解析http协议,目前我的开源软件还只支持get。这个涉及到一个知识点,就是状态机,大家可以先去了解下。
广告一下:本人独立博客如下: http://brucewoo.jd-app.com/,后面的博文逐渐迁移,感谢支持。