I used to work for the government. Now I work for the public.

五种 IO 模型

参考链接

一共有五种 IO 模型

  • 阻塞 IO
  • 非阻塞 IO
  • 多路复用 IO
  • 信号驱动 IO
  • 异步 IO

==其中,前面4种是同步IO,最后一个是异步IO==


IO 简述

《五种 IO 模型》
IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间<-->内核空间、内核空间<-->设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。

对于一个输入(读取到内存)操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。

所以,对于一个网络输入(读取网络资源)的操作,通常包括两个部分

  • 等待网络数据到达网卡 –> 读取到内核空间 (这个过程就是准备数据)
  • 从内核缓冲区复制数据到进程空间
  • 然后具体的用户函数,把结果返回。

IO 操作发生时一般涉及两个对象,一个是调用这个IO的 process (or thread) ,另一个就是系统内核 (kernel)。


IO 模型介绍

我们在介绍IO模型的时候,是根据IO操作的两个阶段,是否被锁了来分类的。

下面我们使用获取一个网络资源来举例(读取数据),假设是 read 方法。

阻塞 IO

  • 准备数据阶段(用户进程阻塞)
  • 复制数据阶段(用户进程阻塞)

当用户进程在读取一个网络资源的时候,内核就开始了IO的第一个阶段:准备数据。

对于网络IO来说,数据一开始还没有准备好(比如还没有收到完整的UDP包),这个时候内核就要等待足够数据到来。

当数据没有准备好,在用户进程这边,用户的进程就会被阻塞,一直等待数据准备好。

当数据准备好了(数据在内核空间中),然后就需要把数据复制到用户进程空间,在复制数据的这个过程中,用户进程仍然是阻塞的。

总结:阻塞IO,就是指当调用读取数据的函数(比如 read),这个函数不立马返回结果,而是阻塞当前进程,直到数据被复制到用户进程空间或者是超时出错才解除阻塞,并返回结果。

缺点:实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用 recv(1024) 的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

一个简单的解决方案

使用多线程或者多进程,多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。

或者是使用线程池,进程池。

非阻塞 IO

  • 准备数据阶段(用户进程非阻塞)
  • 复制数据阶段(用户进程阻塞)

如果采用非阻塞的方式读取数据,当用户调用 read 方法的时候。

如果数据还没有准备好,内核就立马返回一个结果给用户进程(error),用户进程不会阻塞,用户知道数据还没有准备好,那么就可以过一段时间再调用 read 方法进行获取数据,而且在两次 read 的时间间隔中,用户进程还可以做其他操作。

一旦数据准备好了(在内核空间中),而且恰好用户调用了 read 方法,那么数据就会被复制到用户进程空间并返回给用户,复制到用户进程空间这个过程也是阻塞的。

总结:在非阻塞 IO 中,用户在调用 read 方法后,进程没有被阻塞。内核会立马返回数据给进程,这个数据可能是error提示,也可能是最终的结果。如果要得到最终的结果,就需要用户进程主动的多次调用 read 方法。

重复调用 read 方法的过程,称为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

缺点:轮询调用 read 是很费CPU资源的,所以一般我们会在代码中加入 time.sleep(2)。但是加入了 sleep 以后,任务的执行时间长了,因为我们不确定,任务多久执行完成。

多路复用 IO

暂时不考虑

信号驱动 IO

当调用 read 函数的时候,准备数据的过程中用户线程不阻塞,用户线程可以去做其他事情。等到数据准备完了,用户进程会收到一个SIGIO信号,然后可以在信号处理函数中处理数据。

复制数据的阶段,仍然是阻塞的。

异步 IO

相对于同步IO,异步IO不是顺序执行的。用户调用 read 之后,准备数据阶段和复制数据阶段,都是非阻塞的。

数据准备好了后,内核直接复制数据给用户进程空间,然后从内核向进程发送通知,然后用户进程处理数据。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注