IO多路复用
在高并发环境(如网络服务器)中同时处理多个请求的能力就显得尤为重要。很多人可能第一印象就会想到多线程,但是多线程需要进行很多的上下文切换,连接很多时上下文切换的代价就很高。因此采用单线程,但是单线程如何处理并发请求呢?
得益于DMA,多数据到来时直接内存访问,不会丢失数据,因此可以有后序内容
以写一个网络服务器为例。每个网络连接在都是以文件描述符(以下简称fd)的形式存在
select
- 准备文件描述符集合fds
- 用一个数组存储已经创建的文件描述符,一个文件描述符可以看作是一个随机数
- 找出最大的文件描述符max
- 准备文件描述符集合fds
- 初始哈(置零)一个字节数组(bitmap)rset来表示哪个文件描述符有数据到来
- rset默认1024位
- 初始哈(置零)一个字节数组(bitmap)rset来表示哪个文件描述符有数据到来
- 将rset拷贝到内核态,由内核判断fd是否有数据
- 因为判断fd是要在内核态完成的,与其一个个切换然后查询,不然全部传入
- 如果没有数据则内核会阻塞,直到有数据到来,将对应的rset置位然后返回
- 将rset拷贝到内核态,由内核判断fd是否有数据
- 再根据rset判断文件描述符集合中是否有数据,读取有数据的描述符
缺点
- 字节数组大小有限
- 每次rset都要清零
- 拷贝到内核态任有一定开销
- 解阻塞后仍需要遍历fds,O(n)复杂度
poll
工作原理与select类似,做了一些改进
1 | struct pollfd{ |
- 准备文件描述符集合pollfds,用
pollfd
数组储存
- 准备文件描述符集合pollfds,用
- 同select传入内核检查,然后阻塞
- 当有事件发生时
revents
置位,返回
- 当有事件发生时
- 同select传入内核检查,然后阻塞
- 检查
revents
,有事件时进行处理,然后置零
- 检查
改进
- 用pollfd数组存储解决了大小问题
- 让revents置零使得可重用,不用每次都全部置零
epoll
- 创建一个特殊结构epfd用于存储文件描述符集合
- 内核和用户会共享epfd的内存
- 当内核监听到事件发生时,对epfd中的元素进行重排,然后返回事件数
- 因为没有事件发生时会阻塞,所以解阻塞时一定有事件发生,而且事件一定排在前头
- 当内核监听到事件发生时,对epfd中的元素进行重排,然后返回事件数
- 根据事件数n,遍历前n的事件即可
改进
- 解决了内核态拷贝的开销
- 解决了O(n)复杂度问题
应用
- redis
- nginx
- 等