IO模型
转自:https://blog.csdn.net/qq_31815507/article/details/115673210
1.阻塞与非阻塞
阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间执行用户的操作。阻塞指的是用户空间程序的执行状态。传统的IO模型都是同步阻塞IO。
非阻塞是忙、轮询。
2.同步与异步
同步IO,是一种用户空间与内核空间的IO发起方式。
- 同步IO是指用户空间的线程是主动发起IO请求的一方,内核空间是被动接受方。
- 异步IO则反过来,是指系统内核是主动发起IO请求的一方,用户空间的线程是被动接受方。
3.同步阻塞IO(Blocking IO)
应用程序让出CPU,进入等待队列,等待数据准备,并且数据从内核复制到用户空间时也是处于阻塞状态。
直到内核返回后,用户线程才会解除阻塞的状态,重新运行起来。
阻塞的时间段包括:等待数据到达+数据从内核复制到用户空间的两个过程。
- 优点:应用的程序开发非常简单;在阻塞等待数据期间,用户线程挂起。在阻塞期间,用户线程基本不会占用CPU资源。
- 缺点:一般情况下,会为每个连接配备一个独立的线程;反过来说,就是一个线程维护一个连接的IO操作。(因为一个线程一旦IO就被阻塞,所以其他事务都需要一个线程)在并发量小的情况下,这样做没有什么问题。但是,当在高并发的应用场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上阻塞IO模型在高并发应用场景下是不可用的。
2.同步非阻塞NIO(None Blocking IO)
函数返回EWOULDBLOCK表示当前处于非阻塞状态,不需要重复读。但是需要借助while循环?来轮询内核是否准备好了数据。
// 但是上图中左边写”进程阻塞于“,说明轮询的过程其实也是有阻塞的?
- 内核数据到达后,用户线程发起系统调用,用户线程阻塞。内核开始复制数据,它会将数据从内核缓冲区复制到用户缓冲区(用户空间的内存),然后内核返回结果(例如返回复制到的用户缓冲区的字节数)。
过程简单图示:
特点:
应用程序的线程需要不断地进行IO系统调用,轮询数据是否已经准备好,如果没有准备好,就继续轮询,直到完成IO系统调用为止。
优点:
每次发起的IO系统调用,在内核等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。【是否是这样:因为它是经过轮询,所以这段时间其实不会产生切换进程,进程状态仍是运行,占用cpu时间,所以说它是非阻塞的。对照下面的缺点,这样理解是正确的。所以阻塞和非阻塞从宏观上来理解就是进程让不让出cpu,阻塞就是让出cpu】
缺点:
不断地轮询内核,这将占用大量的CPU时间,效率低下。
总体来说,在高并发应用场景下,同步非阻塞IO也是不可用的。一般Web服务器不使用这种IO模型。这种IO模型一般很少直接使用,而是在其他IO模型中使用非阻塞IO这一特性。
3.IO多路复用模型(IO Multiplexing)
如何避免同步非阻塞IO模型中轮询等待的问题呢?这就是IO多路复用模型。
- 在IO多路复用模型中,引入了一种新的系统调用,查询IO的就绪状态。在Linux系统中,对应的系统调用为
select/epoll
系统调用。 - 通过该系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核能够将就绪的状态返回给应用程序。【这里提到的应用程序是指多个还是一个?针对Web服务器来说,可以认为就是当前的进程,认为是一个就行,就是哪个客户端send来了数据准备好了,可以读取了,对应的描述符就处于可读状态】
- 随后,应用程序根据就绪的状态,进行相应的IO系统调用。
在IO多路复用模型中通过select/epoll系统调用,单个应用程序的线程,可以不断地轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可以执行的读写操作。
通过调用select可以监听所有的文件描述符,也就是所有的socket套接字,包括监听socket和连接socket两种。
select本身是阻塞的,只是因为它能够监听多个socket上的读、写、异常事件,而不用一个线程监督一个socket,实现了一个线程可以监督上千个socket的能力,所以多路复用。
这里”多路“指的是多个socket,”复用“指的是都用一个线程调用select来监听所有事件。
当用户进程调用了select查询方法,那么整个线程会被阻塞掉。
-
用户线程获得了就绪状态的列表后,根据其中的socket连接,发起read系统调用,用户线程阻塞。内核开始复制数据,将数据从内核缓冲区复制到用户缓冲区。
-
复制完成后,内核返回结果,用户线程才会解除阻塞的状态,用户线程读取到了数据,继续执行。
【也就是说,进程从挂起导致的等待状态何时转换为就绪呢?宏观上来说是IO准备好了,微观上来说就是,数据从内核缓冲区复制到了用户缓冲区,用户完全可以读了的情况下才会进入就绪状态】
IO多路复用本质上和非阻塞IO本质上一样,不过利用了新的select
系统调用,由内核来负责本来是请求进程该自己做的轮询操作。看似比非阻塞IO还多了一个系统调用开销,不过因为可以支持多路IO,才算提高了效率。
特点:
- 涉及两种系统调用(System Call),
- 一种是select/epoll(就绪查询)
- 一种是IO操作。
- 和NIO模型相似,多路复用IO也需要轮询。负责select/epoll状态查询调用的线程,需要不断地进行select/epoll轮询,查找出达到IO操作就绪的socket连接。
优点:
与一个线程维护一个连接的阻塞IO模式相比,使用select/epoll的最大优势在于,一个选择器查询线程可以同时处理成千上万个连接(Connection)。
系统不必创建大量的线程,也不必维护这些线程,从而大大减小了系统的开销。
本质上,select/epoll系统调用是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是说这个读写过程是阻塞的。
//异步IO先不看了。