流和IO多路复用
# 一. 流, I/O操作, 阻塞
# 1. 流
- 可以进行I/O操作的内核对象
- 文件、管道、套接字……
- 流的入口:文件描述符(fd)
# 2. I/O操作
所有对流的读写操作,我们都可以称之为IO操作。
当一个流中, 在没有数据read的时候,或者说在流中已经写满了数据,再write,我们的IO操作就会出现一种现象,就是阻塞现象,如下图。
# 3. 阻塞
阻塞场景: 你有一份快递,家里有个座机,快递到了主动给你打电话,期间你可以休息。
非阻塞,忙轮询场景: 你性子比较急躁, 每分钟就要打电话询问快递小哥一次, 到底有没有到,快递员接你电话要停止运输,这样很耽误快递小哥的运输速度。
- 阻塞等待
空出大脑可以安心睡觉, 不影响快递员工作(不占用CPU宝贵的时间片)。
- 非阻塞,忙轮询
浪费时间,浪费电话费,占用快递员时间(占用CPU,系统资源)。
很明显,阻塞等待这种方式,对于通信上是有明显优势的, 那么它有哪些弊端呢?
# 二. IO多路复用
# 1. 阻塞IO
就像 socket 通信一样, 连接的客户端一直不发数据,那么服务端线程将会一直阻塞在 read 函数上不返回,也无法接受其他客户端连接。就会发生IO阻塞
# 2. 非阻塞 IO
恳请操作系统为我们提供一个非阻塞的 read 函数。通过轮询或者回调的方法实现
# 3. IO多路复用
doc: https://mp.weixin.qq.com/s/kebjG5UosHmXa7AKCatSrA
- 实现IO多路复用的方法:
- select : 通过遍历(系统遍历)的方式实现, select 只能监听 1024 个文件描述符. windows,linux
- poll: 通过遍历(系统遍历)的方式实现, 不再有文件描述符数量限制. linux
- epoll: 通过回调的方式实现IO多路复用, 没有文件描述符限制, 效率最高. linux
# 3.1 select
系统遍历
select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)
select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)
- 我们可以开设一个代收网点,让快递员全部送到代收点。这个网店管理员叫select。这样我们就可以在家休息了,麻烦的事交给select就好了。当有快递的时候,select负责给我们打电话,期间在家休息睡觉就好了。
- 但select 代收员比较懒,她记不住快递员的单号,还有快递货物的数量。她只会告诉你快递到了,但是是谁到的,你需要挨个快递员问一遍。
# 3.2 poll
系统遍历
poll 也是操作系统提供的系统调用函数。
它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。
# 3.2 epoll
回调(异步 IO 事件唤醒)
内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。