美文网首页
五种 I/O 模型简述

五种 I/O 模型简述

作者: 杰哥长得帅 | 来源:发表于2018-10-29 23:25 被阅读28次

    相关概念

    同步和异步

    描述的是用户线程与内核的交互方式:

    • 同步是指用户线程发起 I/O 请求后需要等待(阻塞)或者轮询内核 I/O 操作(非阻塞)完成后才能继续执行;
    • 异步是指用户线程发起 I/O 请求后仍继续执行,当内核 I/O 操作完成后会通知用户线程,或者调用用户线程注册的回调函数

    同步与异步一般是面向操作系统和应用程序对 IO 操作的层面上来区别的

    同步时:应用程序会直接参与 IO 读写操作,并且应用程序会直接阻塞到某一个方法上,直到数据准备就绪;或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据

    异步时:所有的 IO 读写操作交给操作系统处理,与应用程序没有直接关系,程序不需要关心 IO 读写,当操作系统完成 IO 读写操作时,会给应用程序发送通知,应用程序直接拿走数据即可

    同步 / 异步描述的是执行 IO 操作的主体是谁,同步是由用户进程自己去执行最终的 IO 操作。异步是用户进程自己不关系实际 IO 操作的过程,只需要由内核在 IO 完成后通知它既可,由内核进程来执行最终的 IO 操作

    阻塞和非阻塞

    描述的是用户线程调用内核 I/O 操作的方式:

    • 阻塞是指 I/O 操作需要彻底完成后才返回到用户空间;
    • 非阻塞是指 I/O 操作被调用后立即返回给用户一个状态值,无需等到 I/O 操作彻底完成

    阻塞 / 非阻塞描述的是函数,指访问某个函数时是否会阻塞线程

    I/O 的两个阶段

    阶段 1:等待数据就绪(发起 IO 请求)。网络 I/O 的情况就是等待远端数据陆续抵达;磁盘 I/O 的情况就是等待磁盘数据从磁盘上读取到内核态内存中

    阶段 2:数据拷贝(实际 IO 操作)。出于系统安全,用户态的程序没有权限直接读取内核态内存,因此内核负责把内核态内存中的数据拷贝一份到用户态内存中

    同步 IO 和异步 IO 的区别就在于第二个步骤是否阻塞,如果实际的 IO 读写阻塞请求进程,那么就是同步 IO,因此阻塞 IO、非阻塞 IO、IO 复用、信号驱动 IO 都是同步 IO,如果不阻塞,而是操作系统帮你做完 IO 操作再将结果返回给你,那么就是异步 IO

    阻塞 IO 和非阻塞 IO 的区别在于第一步,发起 IO 请求后是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞 IO,如果不阻塞,那么就是非阻塞 IO

    同步与阻塞区别:

    • 同步和异步关注的是消息通信机制。同步在发出调用后,没得到结果之前不返回,但是一旦返回,就是调用结果;异步是调用发出后立即返回,但是没有返回结果,直到被调用者通过状态、通知、回调函数来通知调用者

    • 阻塞和非阻塞关注的是程序在等待调用结果时的状态。阻塞是指结果返回之前,当前线程会被挂起;非阻塞是指不能立即得到结果时,线程不会被阻塞

    IO 就绪和完成的区别:就绪指的是还需要用户自己去处理,完成指的是内核帮助完成了,用户不用关心 IO 过程,只需要提供回调函数

    select / poll / epoll 从本质上说都是同步非阻塞 IO,select 会收到 IO 就绪的状态,然后通知用户去处理 IO,实际的 IO 操作还需要用户等待内核复制操作

    同步非阻塞 IO 指的是用户调用读写方法是不阻塞的,立刻返回的,而且需要用户线程来检查 IO 状态。需要注意的是,如果发现有可以操作的 IO,那么实际用户进程还是会阻塞等待内核复制数据到用户进程,它与同步阻塞 IO 的区别是后者全程等待

    异步非阻塞 IO 指的是用户调用读写方法是不阻塞的,立刻返回,而且用户不需要关注读写,只需要提供回调操作,内核线程在完成读写后回调用户提供的 callback

    五种 I/O 模型简述

    1. 阻塞 I/O 模型(同步阻塞)
      老李去火车站买票,排队三天买到一张退票
      耗费:在车站吃喝拉撒睡 3 天,其他事一件没干
    Blocking IO
    1. 非阻塞 I/O 模型(同步非阻塞)
      老李去火车站买票,隔 12 小时去火车站问有没有退票,三天后买到一张票
      耗费:往返车站 6 次,路上 6 小时,其他时间做了好多事
    Nonblocking IO

    socket 设置为 NONBLOCK(非阻塞)就是告诉内核,当所请求的 I/O 操作无法完成时,不要将进程睡眠,而是返回一个错误码(EWOULDBLOCK),这样请求就不会阻塞

    I/O 操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。整个 I/O 请求的过程中,虽然用户线程每次发起 I/O 请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的 CPU 的资源

    数据准备好了,从内核拷贝到用户空间

    一般很少直接使用这种模型,而是在其他 I/O 模型中使用非阻塞 I/O 这一特性。这种方式对单个 I/O 请求意义不大,但给 I/O 多路复用铺平了道路

    1. I/O 复用模型(同步阻塞)
      老李去火车站买票,委托黄牛,等着黄牛拿票。黄牛买到后即通知老李去领,然后老李去火车站交钱领票
      select
      黄牛不断去轮询各个渠道问有没有票,轮询渠道数有限制,1024 个
      poll
      同样去轮询各个渠道,但是渠道数没限制
      epoll
      黄牛不轮询了,而是渠道有票数变化时才动身
      耗费:往返车站 1 次,路上 1 小时,黄牛手续费 100 元,看起来比阻塞 I/O 更贵,但是 1 个黄牛可以同时处理好几个客户的票啊
    IO Multiplexing

    I/O 多路复用会用到 select、poll 或者 epoll 函数,这两个函数也会使进程阻塞,但是和阻塞 I/O 所不同的是,这三个函数可以同时阻塞多个 I/O 操作。而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数

    I/O 多路复用模型使用了 Reactor 设计模式实现了这一机制

    select/poll 方法由一个用户态线程负责轮询多个 socket,直到某个阶段 1 的数据就绪,再通知实际的用户线程执行阶段 2 的拷贝。通过一个专职的用户态线程执行非阻塞 I/O 轮询,模拟实现了阶段一的异步化。而 epoll 函数不轮询,而是采用回调函数机制

    从流程上来看,使用 select 函数进行 IO 请求和同步阻塞模型没有太大的区别(只不过是被 select 阻塞,而不是被 recvfrom 阻塞),甚至还多了添加监视 socket,以及调用 select 函数的额外操作,效率更差。但是,使用 select 以后最大的优势是用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 IO 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的

    如果处理的连接数不是很高的话,使用 select/epoll 不一定比使用 multi-threading + blocking IO 的性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接

    1. 信号驱动 I/O 模型(同步非阻塞)
      老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票
      耗费:往返车站 2 次,路上 2 小时,免黄牛费 100 元
    Signal Driven IO

    首先允许 socket 进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据

    1. 异步 I/O 模型(异步非阻塞)
      老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门
      耗费:往返车站 1 次,路上 1 小时,免黄牛费 100 元
    Asynchronous IO

    调用 aio_read 函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回。当内核将数据拷贝到缓冲区后,再通知应用程序

    异步 I/O 模型使用了 Proactor 设计模式实现了这一机制

    五种 I/O 模型比较

    五种 I/O 模型比较

    相关文章

      网友评论

          本文标题:五种 I/O 模型简述

          本文链接:https://www.haomeiwen.com/subject/pajitqtx.html