IO 多路复用是什么意思?

作者:罗志宇
链接:https://www.zhihu.com/question/32163005/answer/55772739
来源:知乎

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

你会怎么做?

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

那么问题就来了:

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

现实上我们的空管同时管几十架飞机稀松平常的事情, 他们怎么做的呢?
他们用这个东西

img

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

这个东西现在还没有淘汰哦,只是变成电子的了而已。。

是不是觉得一下子效率高了很多,一个空管塔里可以调度的航线可以是前一种方法的几倍到几十倍。

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

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

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

imgimg

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

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

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

imgimg

在同一个线程里面, 通过拨开关的方式,来同时传输多个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 修复了pollselect绝大部分问题, 比如:

  • epoll 现在是线程安全的。
  • epoll 现在不仅告诉你sock里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。

epoll 当年的patch,现在还在,下面链接可以看得到:
/dev/epoll Home Page

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

img

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

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

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

ngnix 的设计原则里面, 它会使用目标平台上面最高效的I/O多路复用模型咯,所以才会有这个设置。一般情况下,如果可能的话,尽量都用epoll/kqueue吧。

详细的在这里:
Connection processing methods

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
IO多路复用(I/O Multiplexing)是一种常用的服务器编程技术。它可以同时监控多个文件描述符(File Descriptor),并在其中任意一个文件描述符就绪时通知应用程序,从而实现高效的事件驱动编程。本文将从底层原理及实现方面详细解释IO多路复用技术。 一、I/O模型 在深入理解IO多路复用之前,需要首先了解几种常见的I/O模型。常见的I/O模型有五种,分别为:阻塞I/O、非阻塞I/O、I/O复用(IO Multiplexing)、信号驱动I/O和异步I/O。 1. 阻塞I/O 阻塞I/O是指当应用程序调用一个系统调用来进行I/O操作时,如果此时操作无法立即完成,那么进程将会被阻塞,直到操作完成。在此期间,进程是无法进行其他任何操作的。 2. 非阻塞I/O 非阻塞I/O是指当应用程序进行I/O操作时,如果此时操作无法立即完成,那么系统调用不会阻塞进程,而是立即返回一个错误码。应用程序可以通过轮询的方式不断地检查操作是否完成。 3. I/O复用(IO Multiplexing) I/O复用是指通过一个系统调用来同时监控多个文件描述符的状态,当其中任意一个文件描述符就绪时,通知应用程序进行相应的操作。I/O复用常用的系统调用有select、poll和epoll。 4. 信号驱动I/O 信号驱动I/O是指当应用程序进行I/O操作时,如果此时操作无法立即完成,那么进程不会被阻塞,而是会收到一个SIGIO信号来通知应用程序进行相应的操作。 5. 异步I/O 异步I/O是指当应用程序进行I/O操作时,不需要等待操作完成,而是通过回调函数的方式来处理操作完成的事件。异步I/O常用的系统调用有aio_read和aio_write。 二、I/O复用的实现 I/O复用的实现方式有三种,分别为select、poll和epoll。这三种方式都是通过一个系统调用来同时监控多个文件描述符的状态,从而实现I/O复用。 1. select select是最早的一种I/O复用的机制,它可以同时监控多个文件描述符的状态,当其中一个文件描述符就绪时,通知应用程序进行相应的操作。 select的函数原型如下: ``` int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - nfds:最大文件描述符+1; - readfds:读文件描述符集合; - writefds:写文件描述符集合; - exceptfds:异常文件描述符集合; - timeout:超时时间,如果为NULL,则一直阻塞。 select的具体实现方式是,将多个文件描述符的状态集中起来,通过一个系统调用来同时监控这些文件描述符的状态。当有文件描述符就绪时,select会将就绪的文件描述符从对应的集合中删除,并返回就绪的文件描述符的个数。应用程序可以通过轮询的方式不断调用select来监控文件描述符的状态。 select的缺点是,每次调用都需要将所有的文件描述符的状态集中起来,而且文件描述符的个数是有上限的,通常为1024。这些限制导致select的效率比较低,而且不适用于大规模的并发连接。 2. poll poll是select的改进版,它可以同时监控多个文件描述符的状态,当其中一个文件描述符就绪时,通知应用程序进行相应的操作。 poll的函数原型如下: ``` int poll(struct pollfd *fds, nfds_t nfds, int timeout); ``` 参数说明: - fds:文件描述符数组; - nfds:文件描述符的个数; - timeout:超时时间,如果为-1,则一直阻塞。 poll的具体实现方式是,将多个文件描述符的状态集中起来,通过一个系统调用来同时监控这些文件描述符的状态。当有文件描述符就绪时,poll会将就绪的文件描述符的信息存放在相应的文件描述符数组中,并返回就绪的文件描述符的个数。应用程序可以通过轮询的方式不断调用poll来监控文件描述符的状态。 poll和select的区别在于,poll没有文件描述符的个数限制,而且每次调用不需要将所有的文件描述符的状态集中起来,这使得poll比select效率更高。 3. epoll epoll是Linux内核提供的一种高效的I/O复用机制,它可以同时监控多个文件描述符的状态,当其中一个文件描述符就绪时,通知应用程序进行相应的操作。 epoll的函数原型如下: ``` int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); ``` 参数说明: - size:epoll实例的大小; - epfd:epoll实例的文件描述符; - op:epoll实例的操作类型,包括EPOLL_CTL_ADD、EPOLL_CTL_MOD和EPOLL_CTL_DEL; - fd:要添加、修改或删除的文件描述符; - event:文件描述符的事件类型和状态; - events:就绪的文件描述符的事件类型和状态; - maxevents:最大就绪事件数量; - timeout:超时时间,如果为-1,则一直阻塞。 epoll的具体实现方式是,将多个文件描述符的状态集中起来,通过一个系统调用来同时监控这些文件描述符的状态。当有文件描述符就绪时,epoll会将就绪的文件描述符的信息存放在相应的事件结构体中,并将事件结构体添加到epoll的事件队列中。应用程序可以通过轮询的方式不断调用epoll_wait来获取就绪的事件。 epoll的优点在于:每次调用只需要将就绪的文件描述符的信息存放在事件队列中,而不需要将所有的文件描述符的状态集中起来。这使得epoll比poll和select效率更高,尤其在高并发的情况下,epoll的效率更是明显。 三、I/O多路复用的应用 I/O多路复用可以用于实现高效的事件驱动编程,常见的应用场景有以下几种: 1. 高并发网络编程 I/O多路复用可以用于实现高并发网络编程,通过同时监控多个网络连接的状态,从而实现高效的事件驱动编程。常见的应用场景包括:Web服务器、游戏服务器、聊天室等。 2. 文件I/O I/O多路复用可以用于实现高效的文件I/O操作,通过同时监控多个文件描述符的状态,从而实现高效的事件驱动编程。常见的应用场景包括:异步日志、数据备份等。 3. 定时器 I/O多路复用可以用于实现高效的定时器,通过定时器事件来触发相应的处理函数。常见的应用场景包括:心跳检测、任务调度等。 四、总结 I/O多路复用是一种常用的服务器编程技术,它可以同时监控多个文件描述符,并在其中任意一个文件描述符就绪时通知应用程序,从而实现高效的事件驱动编程。常见的I/O复用实现方式有select、poll和epoll,其中epoll是最常用的一种实现方式,它可以实现高效的高并发网络编程。在实际开发中,需要根据具体的业务场景和实际需求来选择合适的I/O复用实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值