基础概念
在之前写网络编程的时候,我们已经接触过同步,异步,阻塞,非阻塞这些概念,
我们一起来回顾一下:
同步:
同步,就是在客户端发出一个功能调用时,在没有得到结果之前,该调用不返回。
通俗点说,就是必须一件一件事做,等前一件事完了之后才做后一件事。
eg:普通的B/S模式(同步):提交请求->等待服务器处理->处理完毕返回,这期间客户端浏览器不能干任何事
异步:
与同步相对。
当C端一个异步过程调用发出之后,调用者没有立即得到结果,实际处理这个调用的部件在完成后,通过状态,回调来通知调用者。
eg :请求通过事件触发->服务器处理(浏览器仍然可以做其他事情)->处理完毕
阻塞:
阻塞调用,是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态,cpu不会分配时间片,线程暂停运行)函数只有得到结果返回。
举例:比如你去一层(假设是内核缓冲区)取快递,但是不知道什么时候来,你不能干别的事情,只能等着但是可以睡觉(进程处于休眠状态),因为快递来时一定会打电话通知。
非阻塞:
与阻塞相对应,指在不能立即得到结果之前,该函数不会阻塞当前线程,而会立即返回。
举例:在等快递的时候,(用轮询的方式)每隔5分钟去一层(内核缓冲区)去看快递来了没,没来,立即返回;如果快递来了,就放到A楼一层,等你去取。
在了解了这些知识,对于IO模型就很好理解了。
五种IO模型
1、阻塞式IO模型
阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待。
也就是说,
应用程序调用一个I/O函数,导致应用程序阻塞后,等待数据,如果数据没有准备好,一直等待;直到数据准备好,从内核拷贝到用户空间,I/O函数返回成功。
阻塞I/O模型图:
由图可知:
(1)当调用recv()函数时,系统首先检查是否有准备好的数据,如果数据没有准备好,那么系统就处于等待状态;
(2)当数据准备好后,将数据从系统缓冲区拷贝到用户空间,然后函数返回。
2、非阻塞式IO模型
非阻塞IO:如果内核还未将数据准备好,系统调用会直接返回,并且返回EWOULDBLOCK错误码.
也就是说,我们把一个套接口设置为非阻塞就是告诉内存,当所请求的I/O操作无法完成时,返回一个错误;I/O函数会不断的测试数据是否准备好,直到数据准备好为止。
非阻塞I/O模型图:
从图中可以看出来:
(1)这是一个轮询的过程,这对CPU来说是较大的浪费。
(2)每次用户询问内核是否有数据报准备好(文件描述符缓冲区是否就绪),数据报准备好时,就进行拷贝数据报的操作。
(3)当数据报没有准备好的时候,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一次轮询。
3、信号驱动IO模型
信号驱动IO模型:内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作
也就是说,这个模型是由信号进行驱动的。
如下图:
由图可知:
(1)首先建立一个信号处理函数,进程继续运行并不阻塞。
(2)当数据准备好时,进程会收到一个SIGIO信号
(3)在信号处理函数中调用I/O操作函数处理数据报
4、IO多路转接模型
IO多路转接: 能够同时等待多个文件描述符的就绪状态.
也就是说,这种IO模型也是属于阻塞的IO,但是它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞IO模型高效。
如下图:
由图可知:
(1)与阻塞式IO相比,类似,多了一个select函数,对其参数中的文件描述符进行循环监听,当某个文件描述符就绪的时候,就对这个文件描述符进行处理。
(2)直到有数据可读或可写时,才真正调用I/O操作函数。
5、异步IO模型
**异步IO:由内核在数据拷贝完成时,通知应用程序
异步IO使用的不再是read和write的系统接口了,应用工程序调用aio_XXXX系列的内核接口。
如下图:
由图可知:
(1)当应用程序调用aio_read的时候,内核一方面去取数据报内容,另外一方面将程序控制权还给应用进程,进程继续执行。
(2)应用进程继续处理其他事务(应用进程就是一种非阻塞的状态)
(3)当内核的数据报就绪的时候,由内核将数据报拷贝到应用进程中,返回给aio_read中定义好的函数处理程序
总结
任何IO过程中,都包含两个步骤。一是等待,二是拷贝。
在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间。
所以,让IO更高效, 最核心的办法就是让等待的时间尽量少,也就是阻塞越少,理论上效率也是最优。