单线程

四季春风,不厌冬

  1. 概念
  2. 四种模型分析
    1. 同步阻塞IO
    2. 同步非阻塞IO
    3. IO多路复用
    4. 异步IO
  3. 参考

  服务器端常见的四种I/O模型:同步阻塞IO、同步非阻塞IO、IO多路复用、异步IO。

概念

  • IO:不仅仅是对文件的操作,网络编程中,比如 Socket 通信,都是典型的 IO 操作目标.

  • 用户线程、内核:用户线程指客户端或者应用程序;内核指服务器内核。

  • 阻塞、非阻塞
      阻塞IO会一直阻塞住对应的用户线程直到操作完成,而非阻塞IO在内核还在准备数据的情况下会立刻返回。

    内核处理IO请求分两步:1.准备数据(等待磁盘数据加载 到内核),2.拷贝数据(将内核数据拷贝到用户空间)。

  • 同步、异步
      IO模型中的同步和异步指的是真正的IO操作过程是否阻塞了用户线程。如果IO操作阻塞了用户线程,则为同步IO;反之,为异步IO。

    真正的IO操作:以read请求为例,用户线程在发起read请求之前,可能先发起select等函数调用(IO多路复用),而调用select函数并不是真正的IO操作,只有read请求是IO操作。

四种模型分析

同步阻塞IO

  同步阻塞IO是最常见、最简单的IO模型,用户线程对内核发起IO请求(以read为例),用户线程必须等待内核彻底完成数据准备、数据拷贝。
  
同步阻塞IO.png

同步非阻塞IO

  对比同步阻塞IO,同步非阻塞IO的区别在于,用户线程第一次发起read请求后,立即收到回复(内核数据准备状态),该过程不会阻塞用户线程。随后用户线程轮询内核是否准备好数据,数据准备过程都是非阻塞。内核数据准备好,将数据从内核拷贝至用户空间的过程会阻塞用户线程。

同步非阻塞IO.png
  
  虽然数据准备的过程为非阻塞,可以允许有多个任务同时执行,但是轮询造成任务完成的响应延迟(任务可能在两次轮询之间完成),且非常消耗CPU资源,这会造成整体数据吞吐量降低。   

IO多路复用

  同步阻塞IO模型不断地轮询会消耗大量CPU资源,特别是当后台有多个任务同时执行。如果有一个高效率的第三方工具替代用户线程发起轮询,循环查询多个任务的完成状态,当有任务完成时,就去处理它。在IO多路复用模型中,Linux下的select、poll、epoll函数就是高效率的第三方查询工具。
  
IO多路复用.png

  select级别是内核级别的,较用户线程主动轮询而言,select调用会阻塞用户线程,select可以等待多个scoket,能实现同时监听多个端口,当其中任何一个scoket的数据准备好了,就会返回可读,用户线程再发起read请求,将数据从内核拷贝至用户空间,该拷贝过程是阻塞的。
  
  与阻塞IO不同在于,此时的select不是等到socket数据全部到达再处理, 而是有了一部分数据就会通知用户线程来处理。如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。也可以理解为”非阻塞”吧。
  
  对比同步非阻塞IO,多路复用的两个阶段都是阻塞IO,看起来效率并不会高出前者,但是用select的优势在于它可以同时处理多个connection,达到高并发的目的。虽然可以轮询多个scoket,但是不能保证IO之间的顺序。   

IO多路复用:可以理解为多个IO操作复用一个线程,一个调用select函数的线程可以轮询多个scoket。

异步IO

  用户线程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户线程,然后用户态线程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给用户态空间,然后从内核向用户线程发送通知。IO两个阶段,用户线程都是非阻塞的。
    
异步IO.png

  内核将数据拷贝至用户态空间后,用户线程并不会主动去check,如果是实时性系统,或者延迟敏感,那么异步IO显然不太适合。

参考

本文作者 : pengqin.zhou
本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
本文链接 : https://www.zhoupq.com/blog/Linux%E5%9B%9B%E7%A7%8D%E5%B8%B8%E8%A7%81I-O%E6%A8%A1%E5%9E%8B%E5%88%86%E6%9E%90/

本文最后更新于 天前,文中所描述的信息可能已发生改变