同步?阻塞?到底什么是I/O模型

今天来记录下计算机中一些常用的很容易让人搞混的术语:同步/异步 ,阻塞/非阻塞,周末特意花时间重温了下,希望对大家有帮助。

什么是同步?什么是阻塞?这些名词到底起源于哪里?

没有无源之水,一切计算机方案都是解决实际问题的,同步、阻塞起源于I/O模型,而I/O模型解决了什么问题?

I/O模型解决的是计算机CPU(中央处理器)与外设(网卡、磁盘、键盘、鼠标)的速度不匹配的问题。

所以为了解决CPU与外设的速度不匹配问题,造就了I/O模型,而I/O模型衍生出了下面的几种常用的I/O模型术语:

  • 同步阻塞I/O

  • 同步非阻塞I/O

  • I/O多路复用(是同步非阻塞的变形与优化)

  • 信号驱动I/O

  • 异步I/O

由于CPU与外设运行速度的不匹配,CPU运行的快,而外设运行的慢。

假设从网卡中获取网络数据,就需要先将数据读取到内存中,然后CPU去处理内存中的数据。

假设现在运行的一个任务是:启动一个监听程序服务,监听机器上指定端口,然后不断从端口中解析数据,然后会不时地有很多client来连接这个服务,向服务发送一些指令操作,当监听服务监听到不同client发起的不同指令后,就会执行不同的任务,这其实就是一个典型的server/client的socket程序。

当一个client来连接服务端,发送数据后,其实数据会传送到网卡,服务端要接收到这个数据,需要经历下面的过程:

内核从网卡中read数据 -> 内核态数据准备好后,将数据read到用户空间 -> 程序拿到用户数据,这里需要说明的问题是一个进程启动后,进程使用的内存会分为两块,分别是内核态和用户态,内核态内存负责跟外设驱动进行交互,用户态内存负责跟用户程序进行交互,而服务端程序就是运行在用户态的,用户态的程序要获取网卡的数据,就需要内核的协助,需要两次copy,第一次是网卡把数据copy到内核态,第二次是内核把数据copy到用户态。

这时候会有一个另外的问题:CPU空闲的问题。CPU是不断在做指令计算的任务,而把网卡的数据读取到内存中这个过程是在做数据的传输,所以CPU是处于空闲状态的,所以如何高效利用CPU,让CPU全速奔跑就成为一个问题,CPU分配资源的单位是线程,问题也就变成了如何组织线程、管理线程,有效利用CPU,不让其空闲。

下面详细来介绍5种I/O模型:

  • 同步阻塞I/O

传统的I/O模型,Socket读数据的过程是阻塞式的,当client请求过来后,client线程会一直阻塞在那边,一直等到内核态数据拷贝到用户态后,才唤醒client线程。唤醒client线程也是要付出代价的,一旦线程阻塞后,需要切换上下文,内核需要保存线程的状态、内存块和现场,再次唤醒线程,就要恢复线程的上下文,是一个耗费资源的操作。

  • 同步非阻塞I/O

在传统I/O基础上衍生出了一个同步非阻塞的I/O,就是不断地去read,相当于设置了一个while循环,不断去判断是否已经读到了网卡的数据,在数据还没有从网卡到内核态时,每次read都是失败的,但是这时候线程并不阻塞,也不让出CPU,意味着线程其实在read的同时还是能做其他的事情的,但是一旦数据达到内核态后,再去调用read,这次调用是阻塞的,在等待内核态数据拷贝到用户态,所以线程会让出CPU,进入阻塞态,当数据拷贝完成后,再唤醒线程,相比于同步阻塞I/O的情况,同步非阻塞的I/O将线程阻塞的时间缩短了,将原本在网卡到内核态数据拷贝阶段的阻塞给剔除了,换成了不断read的循环操作。

  • I/O多路复用(使同步非阻塞I/O的变形与优化)

在同步非阻塞I/O的基础上,将read操作拆分为两部分,分别是Select+read,原本是不管数据在网卡到内核阶段还是内核到用户态阶段,都用read来判断数据是否达到,所以这种情况下,如果有100个client端连接,就会有100个线程去不断执行read操作来判断、阻塞、读取数据,而拆分成两部分后,Select支持用一个线程来监听多个通道,来判断内核态数据是否已经就绪,一旦就绪后,再调用read操作,调用read操作后依旧是阻塞、读取数据。

相当于原本有100个线程去做内核态数据准备的判断,现在变成了一个Select线程去判断多个通道的内核态数据是否已经准备就绪,节省了资源,因为一个Select线程支持多通道的监听,所以称为多路复用,而后面的read操作在等待内核态数据拷贝到用户态这个阶段依旧是阻塞的。

  • 信号驱动I/O

信号驱动I/O是在同步非阻塞的基础上进行改变,当发起read调用的时候注册一个回调函数,当内核数据准备好之后,内核触发回调函数,应用再次请求read,阻塞,等待内核数据copy到用户态后,再次唤醒应用线程,这个过程也成为半异步IO。

  • 异步I/O

异步I/O解决上面同步I/O以及阻塞的全部问题,在read的同时调用一个回调函数,read可以直接返回,这样client线程就不会阻塞,继续往下执行,直到内核态把数据复制到用户态后,client线程就获取到了数据,然后对数据进行处理,所以client线程整个过程的执行都是异步的,内核会主动通知client线程,数据已经拷贝完成,过程中也没有任何阻塞态存在。

所以总结下来:

同步还是异步,取决于应用程序与内核通信时,是应用程序主动获取内核拷贝的数据,还是被动获取内核拷贝的数据;

阻塞和非阻塞,取决于应用程序调用I/O时,是等待还是立即返回。

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/247214.html

联系我们
联系我们
分享本页
返回顶部