muduo 多线程的处理

一般网络编程中为什么会用到多线程呢,无非是充分利用服务器多核的特性,提高网络并发量,吞吐率等。一般情况下,我们可以轻松想到这样的模型:主线程中监听socket连接事件,当产生新的连接时生成新的线程来处理。这种方式比较直接,但是有些不好处理的地方,例如线程的数量,真的是一个连接开一个线程吗。其实可以用线程池来解决这个问题,那说到线程池,那又如何解决线程与连接对应的问题,毕竟一个连接肯定是始终要在一个线程中处理消息的,否则跨线程处理消息的拼接将会是一个很大的麻烦。还有就是这种直接的方式不好设计接口给用户使用,说直白点就是如何优雅的处理用户数据读和写的问题。而muduo以及大部分的网络库都使用了reactor模式,可以很好的处理这两个问题。

这几天详细读了muduo的网络处理部分,发现多线程处理是整个框架的精华。muduo是基于one loop per thread模型的。那么什么是one loop per thread模型呢?

字面意思上讲就是每个线程里有个loop,即消息循环。我们知道服务器必定有一个监听的socket和1到N个连接的socket,每个socket也必定有网络事件。我们可以启动设定数量的线程,让这些线程来承担网络事件。

每个进程默认都会启动一个线程,即这个线程不需要我们手动去创建,称之为主线程。一般地我们让主线程来承担监听socket的网络事件,然后等待新的连接。至于新连接的socket的事件要不要在主线程中处理,这个得看我们启动其他线程即工作线程的数量。如果启动了工作线程,那么新连接的socket的网络事件一定是在工作线程中处理的。

每个线程的事件处理都是在一个EventLoop的while循环中,而每一个EventLoop都有一个多路事件复用解析器epoller。循环的主体部分是等待epoll事件触发,从而处理事件。主线程EventLoop的epoller会添加监听socket可读事件,而工作线程一开始什么都没有添加(不过每个EventLoop会有一个wakeupChannel_,他会添加可读事件,原因下面会讲到),因为还没有连接的产生。在没有事件触发之前,epoller都是阻塞的,导致线程被挂起。

当有连接来到时,挂起的主线程恢复,会执行新连接的回调函数。在该函数中,会从线程池中取得一个线程来接管新连接socket的处理。前面提到,工作线程没有添加任何事件,那将导致工作线程一直被挂起。那么问题来了,那他是如何处理新连接socket相关事件的呢,也就是说挂起的工作线程什么时候恢复的呢?

上面提到,每个EventLoop还有一个wakeupChannel_,他会添加可读事件。主线程通知工作线程去处理事件的时候,工作线程发现不在本线程的时间片中,于是往wakeupChannel_写入一个int64大小字节的数据,来唤醒沉睡的poller,这样就激发工作线程了。

可以想象,每个线程中有一个泵,泵的作用就是提取消息并且分发给消息的业主调用,一个泵就是一个EventLoop对象。主线程只管socket的监听事件,并产生新的连接。新的连接也会有消息的产生,故需要将新的连接添加到一个消息泵中,这个消息泵可以是新的,也可以和别的连接复用,如下图:

muduo 多线程的处理_第1张图片

 

有关代码,下篇再分析。

你可能感兴趣的:(muduo,linux网络编程,muduo,源码分析)