IO 多路复用是一种同步IO模型:
IO
:网络I/O。多路
:多个文件句柄。复用
:一个线程可以监视多个文件句柄。一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作,没有文件句柄就绪就会阻塞应用程序,交出CPU。
最大优势是减少系统开销,不必创建过多的进程/线程。
IO 多路复用机制要想高效使用,一般还需要把 socket 设置成「非阻塞」模式:
socket 没有数据可读/可写时,应用层去
read/write
socket 也不会阻塞住(内核会返回指定错误,应用层可继续重试),这样应用层就可以去处理其它业务逻辑,不会阻塞影响性能。
select
select会阻塞住监视3类:writefds
(写)、readfds
(读)、和exceptfds
(异常)文件描述符,等有数据、可读、可写、出异常或超时、就会返回;返回后通过遍历fdset整个数组来找到就绪的描述符fd,然后进行对应的IO读写操作。
优点:
- 几乎在所有的平台上支持,跨平台支持性好
缺点:
- 由于是采用 轮询 方式全盘扫描,会随着文件描述符FD数量增多而性能下降。
- 监视/就绪涉及到用户态到内核态之间拷贝 ,并进行遍历。
- 默认单个进程能监听的各种文件描述符 限制 是1024个,可修改宏定义,但是效率仍然慢。
poll
基本原理与select一致,也是 轮询+遍历,但是它没有最大连接数的限制,原因是它的fd集合是基于链表来存储的.
epoll
通过 epoll_ctl
注册fd,一旦fd就绪就会通过 callback
回调机制来激活对应fd,进行相关的io操作。epoll之所以高性能是得益于它的三个函数:
epoll_create
: 系统启动时,在Linux内核里面申请一个 B+树 结构文件系统,返回epoll对象,也是一个fdepoll_ctl
: 每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数epoll_wait
: 轮训所有的callback集合,并完成对应的IO操作
优点:
- 没fd这个限制,所支持的fd上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
- 效率提高,使用 回调 通知而不是轮询的方式,不会随着fd数目的增加效率下降
- 内核和用户空间
mmap
同一块内存实现(mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间)
综合比较
相同点:select,poll,epoll
本质上都是 同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
不同点:
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
数据结构 | bitmap | 数组 | 红黑树 |
最大连接数 | 1024(x86)或 2048(x64) | 无上限 | 无上限 |
最大文件描述符数 | 一般有最大值限制 | 65535 | 65535 |
fd就绪 | 从内核空间拷贝到用户空间 | 从内核空间拷贝到用户空间 | 触发回调不拷贝 |
工作模式 | LT | LT | 支持ET高效模式 |
工作效率 | 线性遍历 O(n) | 线性遍历 O(n) | 事件通知方式 O(1) |
思考题
在 Redis 事件驱动框架代码中,分别使用了 select 和 epoll 两种机制。
为什么 Redis 没有使用 poll 这一机制?
首先,select 并不是只有 Linux 才支持的,Windows 平台也支持。而 Redis 针对不同操作系统,会选择不同的 IO 多路复用机制来封装事件驱动框架。因为 epoll 性能优于 select 和 poll,所以 Linux 平台下,Redis 直接会选择 epoll。而 Windows 不支持 epoll 和 poll,所以会用 select 模型。