网络-五种IO模型/同步异步/阻塞非阻塞

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Fly_Fly_Zhang/article/details/90380005

网络中获取数据的读操作步骤:

  1. 等待数据准备。
  2. 数据从内核空间拷贝到用户空间。
    在这里插入图片描述

同步与异步:

同步与异步是针对应用程序与内核的交互而言。也就是上图的read操作,从缓存中读取数据,如果缓存中数据还没有准备好,如果是同步操作,它会一直等待,直到操作完成。如果是异步操作,那么它会去做别的事情,等待数据准备好,内核通知它,它再去读取数据。

  • 同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。
  • 异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO操作交给内核来处理,完成后内核通知进程IO完成。
同步和异步是相对于操作结果来说,会不会等待结果返回。

阻塞与非阻塞:

应用进程请求IO操作时,如果数据未准备好,如果请求立即返回就是非阻塞,不立即返回就是阻塞。简单来说,就是做一件事如果不能立即获得返回,需要等待,就是阻塞,否则可以理解为非阻塞。

阻塞和非阻塞是相对于线程是否被阻塞。

异步/同步和阻塞/非阻塞的区别:

其实,这两者存在本质区别,他们的修饰对象是不同的。 阻塞和非阻塞是指进程访问的数据如果尚未准备就绪,进程是否需要等待,简单来说这相当于函数内部的实现区别 ,也就是未就绪时是直接返回还是等待就绪
而同步和异步是指访问数据的机制 ,同步一般指主动请求并等待IO操作完毕的方式 ,当数据就绪后再读写的时候必须阻塞,异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知,这可以使进程再数据读写时也不阻塞

同步/异步 与 阻塞/非阻塞的组合方式

故事:老王烧开水
出场人物:老王,两把水壶(水壶,响水壶)

  • 同步阻塞: 效率是最低的,实际程序中,就是fd未设置O_NONBLOCK 标志位的read/write操作。
    老王用水壶烧水,并且站在那里(阻塞),不管水开没开,每隔一定时间看看水开了没(同步->轮询)。
  • 同步非阻塞: 实际上效率是低下的,注意对fd设置O_NONBLOCK 标志位。
    老王用水壶烧水,不再傻傻的站在那里,跑去做别的事情(非阻塞),但是还是会每个一段时间过来看看水开了没,没开就继续去做的事情(同步->轮询)。
  • 异步阻塞: 异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息时被阻塞。比如select函数,假如传入的最后一个timeout函数为NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在select调用处。
    老王用响水壶烧水,站在那里(阻塞),但是不再去看水开了没,而是等水开了,水壶会自动通知它(异步,内核通知进程)。
  • 异步非阻塞: 效率更高,注册一个回调函数,就可以去做别的事情。
    老王用响水壶烧水。跑去做别的事情(非阻塞),等待响水壶烧开水自动通知它(异步,内核通知进程)
socket的fd是什么?

fd是(file descriptor/文件描述符) ,这种一般是BSD Socket的用法,用在Unix/linux系统上。在Unix/linux系统下,一个Socket句柄,可以看做是一个文件,在socket上收发数据,相当于读一个文件进行读写,所以一个socket句柄,通常也用表示文件句柄的fd来表示。

缓存IO

缓存IO又被称为标准IO,大多数文件系统的默认IO操作都是缓存IO。 在linux的缓存IO机制中,操作系统会将IO的数据缓存在文件系统的页缓存(page cache)中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

  • 缓存IO缺点:
    数据在传输过程中需要在应用程序地址空间和内核空间进行多次数据拷贝操作,这些数据拷贝带来的CPU以及内存开销是非常大的。

Unix提供的5种IO模型:

网络IO的本质是socket读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。对于依次IO访问,数据会先被copy到操作系统内核的缓冲区中,然后才会从操作系统内核缓冲区copy到应用程序的地址空间,所以经历两个阶段:

  • 等待所有数据都准备好或者一直在等待数据,有数据的时候将数据copy到系统内核;
  • 将内核缓存中数据拷贝到用户进程中;

对socket流而言:

  • 等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。

  • 把数据从内核缓冲区复制到进程缓冲区中。

  • 阻塞IO: 同步阻塞,即传统的IO模型

  • 非阻塞IO:同步(轮询) 非阻塞,默认创建的socket都是阻塞的,非阻塞IO要求设置未NONBLOCK ,这里说的NIO并非javaNIO库

  • IO多路复用: 同步阻塞(不过可以同时监听多个socket状态,效率提高),即经典的Reactor设计模式 ,java中selector和linux中的epoll都是这种模型。

  • 信号量:信号驱动IO,异步非阻塞。

  • 异步IO: 真正意义上的异步非阻塞(上面都只是数据准备阶段,这个是数据准备和数据处理阶段),即经典的Proactor设计模式
    在这里插入图片描述

阻塞IO(BIO):

在这里插入图片描述
我们recvfrom函数视为系统调用,因为我们区分进程和内核,系统调用一般都会从应用进程空间中运行切换到内核空间中运行,一段时间后又再切换回来;
从图可以看出,从进行系统调用到拷贝数据到应用进程的缓冲区完成,整段时间内都是被阻塞的,在这个过程中,要么正确到达,要么系统调用被信号打断;直到数据报被拷贝到用户进程后,用户才接触阻塞状态,注意,这是用户进程自己进行的阻塞,拷贝也是由用户进程完成 。在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络IO。调用应用程序处于一种不再消费CPU而只是简单等待响应的状态。

  • 优点:能够及时返回数据,无延迟;方便调试。
  • 缺点:需要付出等待的代价。

非阻塞IO(NIO):

在这里插入图片描述
非阻塞,当所请求的IO操作非得把当前进程设置成睡眠才能完成时,不要把当前进程设置成睡眠,而是返回一个错误信息(数据报没有准备好的情况下),此时当前进程可以做其它事情,不用阻塞。
由图可知,前几次进行recvfrom系统调用时都没有数据可以返回,内核均返回EWOULDBLOCK (用于非阻塞模式,不需要重新读或者写),并且不会阻塞当前进程,直到最后一次询问内核缓冲区是否有数据的时候,此时内核缓冲区中已经有准备好的数据,因此当前进程将内核缓冲区数据复制到用户空间,此时系统调用则返回成功。

  • 当一个应用进程像这样对非阻塞的socket循环调用recv/recvfrom 时,则称为轮询;应用进程持续轮询内核,以查看某个操作是否就绪,这样往往消耗大量的CPU时间。因为涉及到用户态和内核态的切换。
  • 优点:相较于阻塞模型,非阻塞不用再等待任务,而是把时间花费在其它任务上,也就是当前线程同时处理多个任务。
  • 缺点: 导致任务完成的响应延迟增大了,因为每隔一段时间才去执行轮询的动作,但是任务可能再两个轮询动作时间间隔内完成,这会导致整体数据吞吐量的降低。

IO复用:指内核一旦发现进程指定一个或者多个IO条件准备读取,他就通知通知该进程。

在这里插入图片描述
有了IO复用,我们就可以调用select / poll,让其阻塞在两个系统调用(1,询问数据是否准备好并且直到数据准备好后才返回。2,内核是否把数据全部复制完成到用户进程)中的某一个之上。
图中阻塞于select 调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,则调用recvfrom 把所读数据报复制到应用进程缓冲区。
非阻塞IO需要用户进程不停的进行轮询,但是IO多路复用不需要不停的轮询,而是派别人去帮忙循环查询多个任务的完成状态,unix/linux 下的select,poll,epoll 就是做这个的;select调用是内核级别的,select轮询相对于非阻塞轮询的区别在于:前者可以等待多个socket,能实现同时对多个IO端口进行监听,当其中任何一个socket的数据准备好了,就能返回可读状态,然后再由进程进行recvfrom系统调用,将数据由内核拷贝到用户进程空间,当然这个过程是阻塞的,用户进程调用了select,那么整个进程就会block
select或poll 调用之后,会阻塞进程;与阻塞IO 阻塞不同的是,此时select不是等到socket数据全部到达再处理,而是有了一部分数据(网络数据是分组到达的) 就会调用用户进程来处理。
如何知道有一部分数据到达了呢? 监视的事情交给内核,内核负责数据的到达处理。

总结就两个重要点:
  • 对多个socket进行监听,只要任何一个socket数据准备好就返回可读;
  • 不需要等一个socket数据全部到达再处理,而是一部分socket的数据到达就通知用户进程进程recvfrom操作。
    select,poll,epoll 的原理就是不断遍历所负责的所有的socket的完成状态,当某个socket有数据到达,就返回可读来通知用户进程来处理。
  • 优点:**能够同时处理多个连接,系统开销小,**系统不需要额外创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
  • 缺点: 如果处理的连结数目不高的话,使用select/poll 的web server 不一定比使用multi-threading(多线程)+blocking IO(阻塞IO) 的web server性能更好,可能延迟更大。(因为阻塞可以保证没有延迟,但是多路复用是处理先存在的数据,所以数据的顺序则不管,导致处理一个完整的任务时间上有延迟)
同步非阻塞(非阻塞IO)和多线程+同步阻塞(阻塞IO)(multi-threading+blocking)

高并发的程序一般使用同步非阻塞方式而非多线程+同步阻塞方式。 要理解这一点,首先要理解并发和并行的区别 。比如去某个部门办事需要依次去几个窗口,大厅的人数是并发度 ,而窗口个数就是并行度 。也就是说并发数指同时进行的任务数(如同时服务的HTTP请求) ,而并行数是可以同时工作的物理资源数量(如CPU核数) 。通过合理调度任务的不同阶段,并发数可以远远大于并行度 ,这就是几个CPU可以支持上万个用户并发请求的原因。在这种高并发条件下,为每个任务(用户请求)创建一个进程或线程的开销非常大。而同步非阻塞方式,可以把多个请求丢到后台去,这就可以在一个进程里服务大量的并发IO请求。

IO多路复用是同步阻塞模型还是异步阻塞模型?

同步时需要主动等待消息通知,而异步则是被动接收消息通知,通过回调,通知,状态等方式来被动获取信息。IO多路复用在阻塞到select阶段时,用户进程是主动等待并调用select函数获取数据就绪状态信息,并且其进程状态为阻塞。所以,把IO多路复用归为同步阻塞模型。

IO复用应用场景:
  • 当客户处理多个描述符时(一般是交互式输入和网络套接口),必须使用IO复用。
  • 当一个客户同时处理多个套接口时,这种情况是有可能的,但很少出现。
  • 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到IO复用。
  • 如果一个服务器既要处理TCP,又要处理UDP,一般要使用IO复用。
  • 如果一个服务器要处理多个服务或者多个协议,一般要使用IO复用。

信号量:

在这里插入图片描述
在这里插入图片描述

首先开启套接字的信号驱动式IO功能,并且通过sigaction(信号处理程序) 系统调用安装一个信号处理函数 ,该函数调用将立即返回,当前进程没有被阻塞 ,继续工作;当数据报准备好的时候,内核为该进程产生SIGIO 的信号,随后既可以在信号处理函数中调用recvfrom 读取数据报,并且通知主循环数据已经准备好等待处理;也可以直接通知主循环让它读取数据报;(其实就是一个待读取的通知和待处理的通知)

异步IO(AIO/异步非阻塞):

在这里插入图片描述

多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。

由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。

另一种解决IO问题的方法是异步IO。 当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

我们调用aio_read 函数,给内核传递描述符,缓冲区指针,缓冲区大小和文件偏移,并且告诉内核当整个操作完成时如何通知我们。该函数调用后立即返回,不被阻塞。
另一方面,从kernel(内核)的角度,当它收到一个aio_read之后,首先它会立即返回,所以不会对用户进程产生任何block ,然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存(copy由内核完成), 当这一切完成以后,kernel会给用户进程发送一个singal或者执行一个基于线程回调函数来完成此次IO处理过程,告诉它read操作完成。

  • 注意:异步IO的拷贝是由内核完成的,其它几种IO都是由用户进程完成的

以上五种IO模型的通俗理解:

活动:演唱会
角色一:满满
角色二:举办方-》售票员
角色三:黄牛
角色四:送票快递员

同步阻塞IO:

满满从家到售票点买票,售票员告诉满满,票明天才能卖。满满直接在售票点等到明天买票,然后回家。

非阻塞IO:

满满从家到演唱会现场向售票员买票,但票还未出来,然后满满就走了,去干别的事情,过了几个小时再来询问票是否出来,还没出来继续干别的事情。重复以上操作,直到票可以买。

IO复用: JAVA->selector / linux->select,poll,epoll

满满想买演唱会的票,打电话给黄牛(select)帮留一张票,票出来后,小明是需要花费时间去售票点买票(阻塞)。

信号IO:

满满想买演唱会门票,给举办方打电话,帮我留意票,可以售票了给我打个电话(打完就返回结果,等待kernel信号通知),我自己来买票。票出来后,满满亲自去售票点买票

异步IO:

满满要看演唱会,给举办方打电话,可以售票了让送票快递员帮我把票送家里,满满就不用自己去专门买票了

几种组合的IO模型:

BIO:同步阻塞模型

分为服务端和客户端,服务端首先创建ServerSocket,等待用户连接,一个用户连接需要线程来做处理。

NIO:同步非阻塞模型
AIO:异步非阻塞模型

推荐博客:

感觉文章不错的同学麻烦动动小手点点关注订阅呗,您的肯定是对我持续更新最大的支持!

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