IO模型
Posted colin-xun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO模型相关的知识,希望对你有一定的参考价值。
首先需要区分几个概念
非阻塞I/O,字符转换,缓冲以及通道
- IO分为内存IO/网络IO/磁盘IO,磁盘IO都是阻塞的
- 阻塞与非阻塞是通过代码来实现的,区别在于是在于发过来操作请求,数据准备好才返回(阻塞)还是直接返回(非阻塞)
- IO读取顺序:磁盘(磁盘IO)/网卡(网络IO)—> 内核缓冲区 —> 用户内存,重点在于后面的过程是否是需要进程阻塞等待的
IO模型
同步阻塞(blocking IO)
A去钓鱼了,你一直在那等,鱼上钩了,然后把鱼钓上来(全程阻塞)普遍使用的IO模型,linux默认的IO模型。
进程调用recvfrom一直到recvfrom返回同步非阻塞(noblocking IO)
B去钓鱼了,放好钩后,然后开始看书,刷抖音去了,过一会看看是不是鱼上钩了。然后等鱼上钩了,把鱼钓上来
这个询问其实询问的是操作系统内核,即文件描述缓冲区是否就绪,准备好了,就进行拷贝数据包的操作。没有数据报准备好时,也不阻塞程序,内核直接返回为准备就绪的信号。
但是这个轮询其实是对CPU来说是一个比较大的消耗很少使用,因为他浪费了大量的CPU资源
进程recvfrom,如果没有准备就绪的话,直接返回EWOULDBLOCK,过段时间再次调用recvfrom,直到正常返回。这个操作其实就是epolling(轮询),epolling内核是一个很占用CPU资源的操作
但是对管道的操作,最好使用非阻塞的方式
- nginx和node对于本地文件的IO使用的是,以线程的方法模拟非阻塞的效果
- 对于静态文件的IO使用的是zero copy(例如sendfile)的效率是非常高的
信号驱动IO(signal blocking IO)
C去钓鱼了,放好钩,但是这个时候他在鱼竿上放了一个小铃铛,然后去看书刷抖音了,等鱼上钩了,铃铛就响了,她就可以把鱼钓上来了
进程告诉内核,数据报准备好的时候,你告诉我一个信号,我对sinal信号进行捕捉,然后调用信号处理函数来他要求套接字一定允许使用异步IO,设置简单,但是困难的是判定SIGIO信号产生的时候程序处于什么状态,UDP的话也就是接收到数据报,或者发生异步错误,但是TCP的情况就太多了。
NTP服务器,它使用UDP,IO多路复用(IO multiplexing)
D也去钓鱼了,但是D带了很多鱼竿,D用的方法和B差不多,他来回走着看是否有鱼上钩了,上钩了就收杆
这样增加了效率,减少了等待的时间,IO多路转接多了一个select函数,其中一个参数是文件描述符的集合,他循环对这些文件描述符进行监听,当某个文件描述符状态改为就绪,就处理这个,select只负责等,recvfrom只负责拷贝。
IO多路复用用到了select()和poll()或者epoll()(2.6开始)函数,主要在这些函数上面阻塞了,调用select的时候,只有准备就绪了才会返回,在单个IO操作下,和同步阻塞是没有任何区别的,高级之处在于对于多个IO的监听,所以他用在多个IO/多类型/多协议的场景下
异步IO (asynchronous IO)
E也想钓鱼,但是E有事情,他让F来帮他钓鱼,F掉到鱼了,就通知E,E来收杆。
应用程序调用aio_read,内核一方面去取数据报内容返回,另一方面将控制权还给应用程序。应用程序继续处理其他事情,是一种非阻塞的。
之前的都只是不管怎么说,最多也就是等待过程非阻塞,现在读都是非阻塞的,这才是异步。
IO的过程其实就两部,一部分等待读或者写就绪(也就是等),一部分就是读或者(数据搬迁)
以上五种模型的效率。A<B<D<C<E
select poll epoll区别
F同样去钓鱼,但是他想钓的鱼是金枪鱼,他告诉G帮他看着,接下来看三种方式下,G的区别
select:G一直去看有鱼上钩了没有,有鱼上钩了,他就去看看这个鱼是不是金枪鱼,是的话他就通知F,但是G最多只能管理1024个杆。无差别轮询,处理的流越多,轮询时间越长,时间复杂度O(n)
poll:poll的方式和select一样,但是不限制数量,它是用链表存储的。时间复杂度O(n)
epoll:epoll可以理解为event poll,epoll的做法相当于给每条鱼都打上了标签,这样鱼上钩了,自动就知道什么鱼了。epoll基于内核的反射机制。在有活跃的socket的时候,系统会调用我们提前设置好的回掉函数。这样就比select/poll轮询方便很多
以上最重要的区别就是epoll把轮询的操作托让给了内核去做,因为内核更高效。但是托让给内核,我们调用了系统调用,如果轮询结果为空,也没有wakeup或新消息处理,这样就会发生空轮询,CPU使用率就会100%
selector.select()
操作是阻塞的,只有被监听的fd有读写操作时,才被唤醒
netty对于此问题解决方案是对Selector的select操作周期进行统计,没完成一次空的select操作就进行一次计数。当在某个周期内发生了N次空轮询,就触发epoll死循环BUG。然后重建Selector,判断是否是其他线程发起的重建请求,若不是讲原来的SocketChannel从旧的Selector上去除注册。注册到新的Selector,并把之前的给关闭
windows 上面的iocp模型是aio模型,他会在哪只操作完IO以后,通过get函数获取一个完成事件的通知。
参考
https://blog.csdn.net/ccj2020/article/details/7739880
https://blog.csdn.net/ZWE7616175/article/details/80591587
https://www.cnblogs.com/wt645631686/p/8528912.html
https://www.cnblogs.com/aspirant/p/9166944.html
以上是关于IO模型的主要内容,如果未能解决你的问题,请参考以下文章