IO模型

Posted colin-xun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO模型相关的知识,希望对你有一定的参考价值。

首先需要区分几个概念

非阻塞I/O,字符转换,缓冲以及通道

  1. IO分为内存IO/网络IO/磁盘IO,磁盘IO都是阻塞的
  2. 阻塞与非阻塞是通过代码来实现的,区别在于是在于发过来操作请求,数据准备好才返回(阻塞)还是直接返回(非阻塞)
  3. IO读取顺序:磁盘(磁盘IO)/网卡(网络IO)—> 内核缓冲区 —> 用户内存,重点在于后面的过程是否是需要进程阻塞等待的

IO模型

  1. 同步阻塞(blocking IO)
    A去钓鱼了,你一直在那等,鱼上钩了,然后把鱼钓上来(全程阻塞)

    普遍使用的IO模型,linux默认的IO模型。
    进程调用recvfrom一直到recvfrom返回

  2. 同步非阻塞(noblocking IO)
    B去钓鱼了,放好钩后,然后开始看书,刷抖音去了,过一会看看是不是鱼上钩了。然后等鱼上钩了,把鱼钓上来
    这个询问其实询问的是操作系统内核,即文件描述缓冲区是否就绪,准备好了,就进行拷贝数据包的操作。没有数据报准备好时,也不阻塞程序,内核直接返回为准备就绪的信号。
    但是这个轮询其实是对CPU来说是一个比较大的消耗

    很少使用,因为他浪费了大量的CPU资源

    进程recvfrom,如果没有准备就绪的话,直接返回EWOULDBLOCK,过段时间再次调用recvfrom,直到正常返回。这个操作其实就是epolling(轮询),epolling内核是一个很占用CPU资源的操作

    但是对管道的操作,最好使用非阻塞的方式

    • nginx和node对于本地文件的IO使用的是,以线程的方法模拟非阻塞的效果
    • 对于静态文件的IO使用的是zero copy(例如sendfile)的效率是非常高的
  3. 信号驱动IO(signal blocking IO)

    C去钓鱼了,放好钩,但是这个时候他在鱼竿上放了一个小铃铛,然后去看书刷抖音了,等鱼上钩了,铃铛就响了,她就可以把鱼钓上来了
    进程告诉内核,数据报准备好的时候,你告诉我一个信号,我对sinal信号进行捕捉,然后调用信号处理函数来

    他要求套接字一定允许使用异步IO,设置简单,但是困难的是判定SIGIO信号产生的时候程序处于什么状态,UDP的话也就是接收到数据报,或者发生异步错误,但是TCP的情况就太多了。
    NTP服务器,它使用UDP,

  4. IO多路复用(IO multiplexing)

    D也去钓鱼了,但是D带了很多鱼竿,D用的方法和B差不多,他来回走着看是否有鱼上钩了,上钩了就收杆

    这样增加了效率,减少了等待的时间,IO多路转接多了一个select函数,其中一个参数是文件描述符的集合,他循环对这些文件描述符进行监听,当某个文件描述符状态改为就绪,就处理这个,select只负责等,recvfrom只负责拷贝。

    IO多路复用用到了select()和poll()或者epoll()(2.6开始)函数,主要在这些函数上面阻塞了,调用select的时候,只有准备就绪了才会返回,在单个IO操作下,和同步阻塞是没有任何区别的,高级之处在于对于多个IO的监听,所以他用在多个IO/多类型/多协议的场景下

  5. 异步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模型的主要内容,如果未能解决你的问题,请参考以下文章

(转载) Linux五种IO模型

IO模型及高性能网络架构分析

IO模型

五种IO模型

五大IO模型

并发编程 - IO模型 - 1.io模型/2.阻塞io/3.非阻塞io/4.多路复用io