五种IO模型(通过例子说明)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yubujian_l/article/details/83244701

通俗地讲,在网络环境下IO可分为两个部分:等待和数据迁移。

如果要提高IO效率,则需要减少等待时间。

五种IO模型分别为:阻塞式IO、非阻塞式IO、信号驱动IO、多路复用IO及异步IO。前四个为同步IO。

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回。就得到了返回值;换句话说,就是由调用者主动等待这个调用的结果。
  • 异步则相反,调用发出之后,这个调用就直接返回了,所以没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

一、阻塞式IO

 阻塞式IO:IO即input/output,阻塞式IO指的是“一旦输入/输出工作没有完成,则程序阻塞,直到输入/输出工作完成”。在目前,我们从书本上学到的语法用的基本都是阻塞式IO。比如c语言的stdio.h库的所有函数(包含scanf(),getchar(),gets()等函数),Java的BIO(比如各类输入输出流)。他们都是不见黄河心不死的好汉。在你满足他们的条件之前,不让你的程序继续往下跑。最简单的例子:c语言的scanf()函数——当你scanf()要求输入两个数字时,你只输入一个数字,它也不会让你继续执行接下来的代码的。

举个栗子:A拿着一支鱼竿在河边钓鱼,一直在鱼竿前等,在等待的过程中啥也不干,就一心一意等待鱼上钩。只有当鱼上钩的时候,才会结束掉这个等待状态。

在内核将数据准备好之前,系统调用会一直等待所有的套接字,默认的是阻塞方式。

 其实,我们例子中所说的鱼竿就是这一个文件描述符。这个模型是我们最常见的,程序调用和我们编写的基本程序是一致的。

fd = connect();
write(fd);
read(fd);
close(fd);

程序的read必须在write之后执行,当write阻塞住了,read就不能执行下去,一直处于等待状态。

二、非阻塞式IO

非阻塞式IO:非阻塞式IO其实也并非完全非阻塞,通常都是通过设置超时来读取数据的。未超时之前,程序阻塞在读写函数上;超时后,结束本次读取,将已读到的数据返回。通过不断循环读取,就能够读到完整数据了。如果多次连续超时读到空数据的话,则可以断开。C语言的Socket可以使用setsockopt()来设置recv()超时(通常也就Socket需要考虑超时)。

举个栗子:B拿着一只鱼竿在河边钓鱼,但是B在等待鱼上钩的过程中,同时也在干别的事情(例如:玩手机、打游戏等),但是在干别的事的时候会每隔一定的时间检查一下是否有鱼上钩,一旦检查到有鱼上钩,就会停下手中的事情,把鱼钓上来。

其实,B在检查鱼竿是否有鱼,是一个轮询的过程。

每次客户询问内核是否有数据准备好,即文件描述符缓冲区是否就绪。当有数据报准备好时,就进行拷贝数据报的操作。当没有数据报准备好时,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一个轮寻。

但是,轮寻对于CPU来说是较大的浪费,一般只有在特定的场景下才使用。

三、信号驱动IO

信号驱动IO是指:进程预先告知内核,使得 当某个socketfd有events(事件)发生时,内核使用信号通知相关进程。

举个栗子:C拿着一支鱼竿在河边钓鱼,但与A、B不同,C在钓鱼的同时也干别的事,不过C在鱼竿上挂一个铃铛,当有鱼上钩的时候,这个铃铛就会响,C就将鱼钓上来。

信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对SIGIO信号进行捕捉,并且调用我的信号处理函数来获取数据报。

四、多路复用IO

多路复用IO实际上就是用select, poll, epoll监听多个io对象,当io对象有变化(有数据)的时候就通知用户进程。好处就是单个进程可以处理多个socket。

举个栗子:D也在河边钓鱼,不过D比较有钱,他有很多根鱼竿,同时等待多根鱼竿,D不断查看每根鱼竿是否有鱼上钩。提高了效率,减少了等待时间。

多路复用IO是多了一个select函数,select函数有一个参数是文件描述符集合,对这些文件描述符进行循环监听,当某个文件描述符就绪时,就对这个文件描述符进行处理。

其中,select只负责等,recvfrom只负责拷贝。 
多路复用IO是属于阻塞IO,但可以对多个文件描述符进行阻塞监听,所以效率较阻塞IO的高。

(1)当用户进程调用了select,那么整个进程会被block;

(2)而同时,kernel会“监视”所有select负责的socket;  

(3)当任何一个socket中的数据准备好了,select就会返回;

(4)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

       所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回

    这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

  所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用多线程 + 阻塞 IO的web server性能更好,可能延迟还更大。

  select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

  在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

五、异步IO

举个栗子:E也想钓鱼,但是E有其他事情,抽不开身,就雇了F帮他钓鱼,一旦有鱼上钩,F就通知E有鱼上钩了,E再来将鱼钓上来。

当应用程序调用aio_read时,内核一方面去取数据报内容返回,另一方面将程序控制权还给应用进程,应用进程继续处理其他事情,是一种非阻塞的状态。

当内核中有数据报就绪时,由内核将数据报拷贝到应用程序中,返回aio_read中定义好的函数处理程序。

可以看出,阻塞程度:阻塞IO>非阻塞IO>多路复用IO>信号驱动IO>异步IO,效率是由低到高的

展开阅读全文

没有更多推荐了,返回首页