多路复用,讲的很明白

做者:罗志宇
连接:https://www.zhihu.com/question/32163005/answer/55772739
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
 html

假设你是一个机场的空管, 你须要管理到你机场的全部的航线, 包括进港,出港, 有些航班须要放到停机坪等待,有些航班须要去登机口接乘客。linux

你会怎么作?nginx

最简单的作法,就是你去招一大批空管员,而后每人盯一架飞机, 从进港,接客,排位,出港,航线监控,直至交接给下一个空港,全程监控。数组

那么问题就来了:安全

  • 很快你就发现空管塔里面汇集起来一大票的空管员,交通稍微繁忙一点,新的空管员就已经挤不进来了。
  • 空管员之间须要协调,屋子里面就1, 2我的的时候还好,几十号人之后 ,基本上就成菜市场了。
  • 空管员常常须要更新一些公用的东西,好比起飞显示屏,好比下一个小时后的出港排期,最后你会很惊奇的发现,每一个人的时间最后都花在了抢这些资源上。

 

现实上咱们的空管同时管几十架飞机稀松日常的事情, 他们怎么作的呢?
他们用这个东西服务器

这个东西叫flight progress strip. 每个块表明一个航班,不一样的槽表明不一样的状态,而后一个空管员能够管理一组这样的块(一组航班),而他的工做,就是在航班信息有新的更新的时候,把对应的块放到不一样的槽子里面。并发

 

这个东西如今尚未淘汰哦,只是变成电子的了而已。。ide

是否是以为一会儿效率高了不少,一个空管塔里能够调度的航线能够是前一种方法的几倍到几十倍。函数

若是你把每个航线当成一个Sock(I/O 流), 空管当成你的服务端Sock管理代码的话.工具

第一种方法就是最传统的多进程并发模型 (每进来一个新的I/O流会分配一个新的进程管理。)
第二种方法就是I/O多路复用 (单个线程,经过记录跟踪每一个I/O流(sock)的状态,来同时管理多个I/O流 。)

其实“I/O多路复用”这个坑爹翻译多是这个概念在中文里面如此难理解的缘由。所谓的I/O多路复用在英文中其实叫 I/O multiplexing. 若是你搜索multiplexing啥意思,基本上都会出这个图:

因而大部分人都直接联想到"一根网线,多个sock复用" 这个概念,包括上面的几个回答, 其实无论你用多进程仍是I/O多路复用, 网线都只有一根好伐。多个Sock复用一根网线这个功能是在内核+驱动层实现的

重要的事情再说一遍: I/O multiplexing 这里面的 multiplexing 指的实际上是在单个线程经过记录跟踪每个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流. 发明它的缘由,是尽可能多的提升服务器的吞吐能力。

 

是否是听起来好拗口,看个图就懂了.

 


在同一个线程里面, 经过拨开关的方式,来同时传输多个I/O流, (学过EE的人如今能够站出来义正严辞说这个叫“时分复用”了)。

 

什么,你尚未搞懂“一个请求到来了,nginx使用epoll接收请求的过程是怎样的”, 多看看这个图就了解了。提醒下,ngnix会有不少连接进来, epoll会把他们都监视起来,而后像拨开关同样,谁有数据就拨向谁,而后调用相应的代码处理。

------------------------------------------
了解这个基本的概念之后,其余的就很好解释了。

select, poll, epoll 都是I/O多路复用的具体的实现,之因此有这三个鬼存在,实际上是他们出现是有前后顺序的。

I/O多路复用这个概念被提出来之后, select是第一个实现 (1983 左右在BSD里面实现的)。

select 被实现之后,很快就暴露出了不少问题。

  • select 会修改传入的参数数组,这个对于一个须要调用不少次的函数,是很是不友好的。
  • select 若是任何一个sock(I/O stream)出现了数据,select 仅仅会返回,可是并不会告诉你是那个sock上有数据,因而你只能本身一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就很有海天盛筵的豪气了。
  • select 只能监视1024个连接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见FD_SETSIZE。
  • select 不是线程安全的,若是你把一个sock加入到select, 而后忽然另一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,若是你丧心病狂的居然关掉这个sock, select的标准行为是。。呃。。不可预测的, 这个但是写在文档中的哦.

“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
霸不霸气

因而14年之后(1997年)一帮人又实现了poll, poll 修复了select的不少问题,好比

  • poll 去掉了1024个连接的限制,因而要多少连接呢, 主人你开心就好。
  • poll 从设计上来讲,再也不修改传入数组,不过这个要看你的平台了,因此行走江湖,仍是当心为妙。

其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个连接简直就是神同样的存在了,select很长段时间已经知足需求。

可是poll仍然不是线程安全的, 这就意味着,无论服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你固然能够那多进程来配合了,不过而后你就有了多进程的各类问题。

因而5年之后, 在2002, 大神 Davide Libenzi 实现了epoll.

epoll 能够说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 好比:

  • epoll 如今是线程安全的。
  • epoll 如今不只告诉你sock组里面数据,还会告诉你具体哪一个sock有数据,你不用本身去找了。

 

epoll 当年的patch,如今还在,下面连接能够看获得:
/dev/epoll Home Page

贴一张霸气的图,看看当年神同样的性能(测试代码都是死链了, 若是有人能够刨坟找出来,能够研究下细节怎么测的).


横轴Dead connections 就是连接数的意思,叫这个名字只是它的测试工具叫deadcon. 纵轴是每秒处理请求的数量,你能够看到,epoll每秒处理请求的数量基本不会随着连接变多而降低的。poll 和/dev/poll 就很惨了。

 

但是epoll 有个致命的缺点。。只有linux支持。好比BSD上面对应的实现是kqueue。

其实有些国内知名厂商把epoll从安卓里面裁掉这种脑残的事情我会主动告诉你嘛。什么,你说没人用安卓作服务器,尼玛你是看不起p2p软件了啦。

而ngnix 的设计原则里面, 它会使用目标平台上面最高效的I/O多路复用模型咯,因此才会有这个设置。通常状况下,若是可能的话,尽可能都用epoll/kqueue吧。

详细的在这里:
Connection processing methods

PS: 上面全部这些比较分析,都创建在大并发下面,若是你的并发数太少,用哪一个,其实都没有区别。 若是像是在欧朋数据中内心面的转码服务器那种动不动就是几万几十万的并发,不用epoll我能够直接去撞墙了