先理解名字:
IO多路复用=多个IO连接复用同一个线程
定义
IO多路复用是一种同步IO模型,实现一个线程监视多个文件描述符fd;一旦某个文件描述符fd就绪,就能够通知应用程序进行相应的读写操作;没有fd就绪时会阻塞应用程序,交出cpu
上面的这句定义未免有些抽象,我们来尝试理解一下
理解IO多路复用原理:
我们拿到了一堆文件描述符fd
(网络相关、磁盘文件相关等等,任何文件描述符都可以)
通过调用某个函数告诉内核:“这个函数你先不要返回,你替我监视着这些描述符,当这堆文件描述符中有可以进行I/O读写操作的时候你再返回”
当调用的这个函数返回后,我们就能知道哪些文件描述符
可以进行I/O操作了。
图示
1、与多线程技术相比
I/O多路复用技术的最大优势
是系统开销小
,系统不必创建多个线程,也不必维护这些线程,从而大大减小了系统的开销。
2、与阻塞式I/O的对比
两种模式的两个阶段都阻塞,那区别在哪里呢?
虽然第一阶段都是阻塞,但是阻塞式I/O如果要接收更多的连接,就必须创建更多的线程。
而I/O复用模式下只要单个线程
来循环处理这些连接事件就可以了,一旦达到“就绪”
的条件,就可以立即执行真正的I/O操作。
这就是I/O复用与传统的阻塞式I/O最大的不同。也正是I/O复用的精髓所在。
3、IO多路复用是同步阻塞IO
,网上有很多说是非阻塞的其实不对
因为用户进程在等待数据
和拷贝数据
这两个阶段都是阻塞的。
从内核
的角度,内核等待fd就绪是阻塞的,如果没有任何一个fd就绪则一直等待。
但有一点是不阻塞
的,就是复制数据
时内核
不用等待,当有就绪条件满足的时候,它直接复制,其余时间则处理别的就绪的条件。这也是大家一直说的非阻塞I/O。实际上就是指的这个地方的非阻塞
。
在Linux世界中有这样三种机制可以用来进行I/O多路复用
select
poll
epoll
在select这种I/O多路复用机制下,我们需要把想监控的fd集合
通过函数参数
的形式告诉select,然后select会将这些fd集合
拷贝到内核
中,然后遍历所有fd
找到就绪fd
返回给应用程序。
select三个缺点:
用户监控的fd集合不能超过1024个
, 因为数据拷贝是有性能损耗的,当线程监控的fd过多会导致拷贝数据的时间过多,从而使得性能变慢。
每次调用select,都需要把所有的fd集合
从用户态拷贝
到内核态,fd越多开销则越大;
每次调用select都需要在内核遍历所有fd
来找到具体是哪个文件描述符可以读写,这个开销在fd很多时也很大。
poll是基于链表来存储的,所以poll没有最大连接数的限制
但是另外两个缺点没有解决
Epoll只关心就绪的连接,不关心连接总数,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll
Epoll的优点
文件描述符数量不再受限
(1G的内存上能监听约10万
个端口);解决缺点1
使用内存映射mmap
技术,节省了用户态和内核态间数据拷贝
的资源消耗;解决缺点2
不再遍历所有fd
,只有就绪的fd才会执行回调函数。I/O的效率不会随着监视fd的数量的增长而下降;解决缺点3
表面上看epoll的性能最好,但是在连接数少
并且连接都十分活跃
的情况下,select和poll的性能可能比epoll好
但是当并发连接(fd)
较多时,Epoll的优势便真正展现出来。