几种i/o模型:阻塞、非阻塞、异步、多路复用

164 阅读4分钟

先介绍下几个概念:

1。阻塞、非阻塞是指进程/线程的状态。cpu只有一个,进程却有多个,操作系统为了让多个进程都可以使用到cpu,须进行调度管理,此背景下将进程主要分为几个关键状态:就绪、阻塞(等待)、运行。

a。就绪态:表明当前程序一切准备妥当,只等待操作系统将cpu使用权切换到自己。

b。阻塞态:当前程序有卡点问题,没必要让操作系统将cpu使用权切换到自己,浪费cpu资源。待卡点问题解决后,变为就绪态。这个卡点问题一般都是由i/o操作引起,比如等待从磁盘读取文件,等待键盘输入,等待从网卡读取网络连接的数据等等。

2。异步:当程序中某个调用比较耗费时间,而程序后续部分有些代码又不依赖这个调用返回时,我们可以让这个调用立刻return并将依赖这个结果值的代码包在一起组成一个回调函数进行注册,这样当前程序可以继续执行不依赖这个调用结果值的代码。而当这个耗时调用完成时候,再执行当时注册的回调函数。这样将程序分成两部分执行,就不会浪费好不容易得到的cpu资源了。

3。i/o多路复用,一般用在服务端的网络编程中。一个进程/线程可以监听多个网络连接,当其中某个/某几个连接有数据时候,当前程序可以拿到网卡收到的数据进行下一步的处理。上面的那三种呢一般是从单个i/o文件读取数据。如果用上面三种方式来编写一个支持多链接的服务端网络程序,只能使用多进程或者多线程。

4。再来看看i/o操作的基本流程。首先我们编写的应用程序,是不能直接操作i/o 设备,无论是c、java、php、python、node语言,对文件的读取都是对系统调用read的封装。以读磁盘文件为例,应用程序调用内核read系统调用,内核向磁盘控制控制器发出读指令,磁盘收到指令后经过一段长时间的读取,将目标数据读到磁盘控制器的数据寄存器中并向cpu发出“中断”信号表明自己已经准备好数据,cpu收到“中断”信号后执行“中断”处理程序,中断处理程序将磁盘控制器数据寄存器中的数据读到内存的内核缓冲区中,然后再将数据从内核缓冲区复制到应用程序。所以基本流程如下:

应用程序read-->系统内核read系统调用->磁盘查找数据->将磁盘查找到的数据读到内核缓冲区->将内核缓冲区的数据复制到应用程序->应用程序拿到数据后进行下一步处理。

看此链路,其中耗时较长的两部分是:磁盘查找数据并将查找完的数据复制到内核缓冲区、将内核缓冲区的数据复制到应用程序。 也就是在这两部分时间段不同的处理方式使i/o类型分为了阻塞i/o,非阻塞i/o,异步i/o。

阻塞i/o:调用read后,当前程序进入阻塞态,等数据从磁盘找到并读到内核缓冲区,再从内核缓冲区复制到当前程序缓存区后,read返回,程序变为就绪态,等待操作系统切换到当前程序后继续运行。如下图

非阻塞i/o:发出read请求后,内核缓冲区发现没数据立刻返回error并向磁盘控制器发出请求指令,应用程序收到error后,继续轮询,一直询问内核有没有数据,直到磁盘控制器将数据查找出来并复制到内核缓冲区,再从内核缓冲区复制到应用进程缓冲区,read返回正确的结果。此种类型需不断轮询才能拿到想要的数据,当前程序在轮询的前几次,内核缓冲区并无数据,磁盘查询数据并复制到内核缓冲区,此阶段当前进程并未进入阻塞态。在轮询中内核缓冲区有数据的这次,数据需要从内核缓冲区复制到应用程序缓冲区,此阶段进入阻塞态。如下图:

异步i/o:发出read请求后,立即return。应用程序继续往下执行,而内核会在磁盘控制器找到数据复制到内核缓冲区,然后从内核缓冲区复制到应用程序缓冲区后给应用程序发出一个信号,表示数据已经读取完毕,应用程序继续对此数据进行下一步处理。应用程序整个过程都未进入阻塞态。如下图

i/o多路复用,一般用在服务端网络编程中,可单独写。