原文参考:

  1. https://blog.csdn.net/ldw201510803006/article/details/119767467
  2. https://www.liaoxuefeng.com/wiki/1016959663602400/1017606916795776

前言

[!QUESTION] IO是什么?

  1. IO是计算机内存与外部设备进行数据拷贝的过程。
  2. 由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接操作。

IO模型要解决什么问题

数据读取的基本流程:

  1. 用户发起IO请求
  2. 用过调用read()间接调用内核操作从网卡读取数据
  3. 数据从网卡拷贝到内核缓冲区
  4. 数据从内核缓冲区拷贝到用户缓冲区

这里面设计到三个硬件:CPU,内存,网卡(外部物理设备),这三者的数据操作速度有巨大的差别,用户读取数据时该阻塞等待?非阻塞轮询获取数据?内核回调通知?这些就是IO模型解决的问题。

IO模型的区别

当用户线程发起IO操作后,网络读取操作会发生以下两个过程

  1. 数据从网卡拷贝到内核缓冲区
  2. 数据从内核缓冲区拷贝到用户缓冲区

不同IO模型之间的区别就是:他们实现这两个步骤的方式不同

同步阻塞IO模型(BIO)

什么被阻塞了?用户线程被阻塞了

  1. 用户线程发起IO调用,执行read方法,用户线程阻塞等待,让出CPU
  2. 内核等待网卡数据到来 ==> 数据从网卡拷贝到内核缓冲区==> 数据从内核缓冲区拷贝到用户缓冲区
  3. 内核唤醒用户线程,用户线程执行后续操作

简化版流程图:
image-20240324213335344

用户进程被阻塞时系统在做什么

对于磁盘IO,直接从磁盘中读取到内核缓冲区(这个过程可以不需要cpu参与)。而对于网络IO,应用程序需要等待客户端发送数据,如果客户端还没有发送数据,对应的应用程序将会被阻塞,直到客户端发送了数据,该应用程序才会被唤醒,从Socket协议找中读取客户端发送的数据到内核空间,然后把内核空间的数据copy到用户空间,供应用程序使用。

同步非阻塞IO模型(NIO)

用户线程不断的调用read方法去内核缓冲区查询数据,如果没有数据则返回失败,当有数据时,在数据从内核缓冲区拷贝到用户缓冲区的过程中,用户线程阻塞,让出CPU,等待数据拷贝完成后,唤醒用户进程

简化流程图:
image-20240324223127750

需要注意的是:这里是用户进程去轮询读取数据,和IO多路复用的专用的线程去轮询处理是不同的。

IO多路复用

参考:[[0x400 IO多路复用]]
只是监测数据

异步非阻塞IO模型

用户线程发起 read 调用的同时注册一个回调函数,read 立即返回,等内核将数据准备好后,再调用指定的回调函数完成处理。在这个过程中,用户线程一直没有阻塞。
image-20240330232401743

异步IO模型中的回调函数通常是在内核数据准备就绪后由内核来调用的,而不是在用户线程中直接调用。这允许用户线程在等待IO操作完成的同时继续执行其他任务,提高了应用程序的并发性和效率。回调函数的执行通常不会阻塞用户线程,它们可以在专门的内核线程或者事件处理线程中执行,或者通过某种机制(如信号)在用户空间中异步执行。

异步非阻塞IO和同步非阻塞IO的区别

异步IO通常适用于需要长时间等待的IO操作,而非阻塞IO适用于需要快速响应的场合。

同步与异步:指应用程序在与内核通信时,数据从内核空间到应用空间的拷贝,是由内核主动发起还是由应用程序来触发;内核主动发起则是异步,反之为同步