I/O模型

338 阅读6分钟

同步与异步

同步与异步主要是从消息通知机制角度来说的。

当一个同步调用发出后,调用者要一直等待返回消息(结果)后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果),实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。异步是调用完成后由别人来通知他。

阻塞非阻塞

阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。

阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

如果在这个等待的过程中,等待者除了等待消息通知之外不能做其它的事情,那么该机制就是阻塞的。

阻塞与同步是不同的。

如果这个线程在等待函数返回时,仍在执行其他消息处理,那么这就是同步非阻塞。

如果这个线程在等待函数返回时,没有执行其他消息处理,而是挂起等待,那么就是同步阻塞。

一些概念

进程的切换是很耗资源的。

对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

1. 等待数据准备 (Waiting for the data to be ready)

2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

四种I/O模型

可以站在Server端去理解。

阻塞型I/O

è¿éåå¾çæè¿°

​​当应用进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在进程这边,整个应用进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,应用进程才解除block的状态,重新运行起来。

在内核执行I/O的两个阶段,都是阻塞的。每个连接都需要配套一个线程,不适合高并发的情况。

非阻塞I/O

è¿éåå¾çæè¿°​​当所请求的I/O操作不能满足要求时候,不把本进程投入睡眠,而是返回一个错误。也就是说当数据没有到达时并不等待,而是以一个错误返回。并且进程会多次轮询的请求I/O操作。

应用程序的线程需要不断的进行 I/O 系统调用,轮询数据是否已经准备好,如果没有准备好,继续轮询,直到完成系统调用为止 。这样,好处是线程不需要一直阻塞,但是需要不断地进行I/O系统调用,不断轮询,浪费CPU。不断发起I/O操作及其浪费CPU资源。

同步非阻塞就是 “每隔一会儿瞄一眼进度条” 的轮询(polling)方式。

I/O多路复用

输入图片说明

由于同步非阻塞方式需要不断主动轮询,轮询占据了很大一部分过程,轮询会消耗大量的CPU时间,但是服务器端可能会有多个连接,这样他对每一个连接都这样做,要是连接数量太多时,是不适合的

IO多路复用有两个特别的系统调用select、poll、epoll函数。select调用是内核级别的,select轮询相对非阻塞的轮询的区别在于---前者可以等待多个socket,能实现同时对多个IO端口进行监听,当其中任何一个socket的数据准好了,就能返回进行可读,然后进程再进行recvform系统调用,将数据由内核拷贝到用户进程,当然这个过程是阻塞的

这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作(只需要阻塞一个select函数)。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时(注意不是全部数据可读或可写),才真正调用I/O操作函数。

上面的图和blocking IO的图其实并没有太大的不同,在两个阶段都需要阻塞。当连接数量很少的时候,I/O多路复用可能比BIO效率还要低,因为I/O多路复用需要多执行一个select内核操作。但是I/O多路复用的优势在于他可以处理更多的连接,而不是处理单个连接速度更快

与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源。

异步I/O

输入图片说明

用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的

总结

注意:同步非阻塞I/O和I/O多路复用,在返回可读条件后,都需要再调用一次I/O操作,进行复制数据。

首先一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。

参考:

https://www.jianshu.com/p/486b0965c296

www.cnblogs.com/crazymakerc…