基础概念:
同步、异步、阻塞、非阻塞
同步 & 异步
同步与异步是针对多个事件(线程/进程)来说的。
- 如果事件A需要等待事件B的完成才能完成,这种串行执行机制可以说是同步的,这是一种可靠的任务序列,要么都成功,要么都失败。
- 如果事件B的执行不需要依赖事件A的完成结果,这种并行的执行机制可以说是异步的。事件B不确定事件A是否真正完成,所以是不可靠的任务序列。
同步异步可以理解为多个事件的执行方式和执行时机如何,是串行等待还是并行执行。同步中依赖事件等待被依赖事件的完成,然后触发自身开始执行,异步
中依赖事件不需要等待被依赖事件,可以和被依赖事件并行执行,被依赖事件执行完成后,可以通过回调、通知等方式告知依赖事件。
阻塞 & 非阻塞
阻塞与非阻塞是针对单一事件(线程/进程)来说的。
- 对于阻塞,如果一个事件在发起一个调用之后,在调用结果返回之前,该事件会被一直挂起,处于等待状态。
- 对于非阻塞,如果一个事件在发起调用以后,无论该调用当前是否得到结果,都会立刻返回,不会阻塞当前事件。
阻塞与非阻塞可以理解为单个事件在发起其他调用以后,自身的状态如何,是苦苦等待还是继续干自己的事情。非阻塞虽然能提高CPU利用率,但是也带来了系统线程切换的成本,需要在CPU执行时间和系统切换成本之间好好估量一下。
同步阻塞
应用程序执行系统调用,应用程序会一直阻塞,直到系统调用完成。应用程序处于不再消费CPU而只是简单等待响应的状态。当响应返回时,数据被移动到用户空间的缓冲区,应用程序解除阻塞。
同步阻塞I/O模型.
同步非阻塞
设备以非阻塞形式打开,I/O操作不会立即完成,read操作可能会返回一个错误代码。应用程序可以执行其他操作,但需要请求多次I/O操作,直到数据可用。
同步非阻塞形式实际上是效率低下的,因为:
- 应用程序需要在不同的任务之间切换。异步非阻塞是你只需要执行当前任务,系统调用会主动通知你,不用频繁切换。
- 数据在内核中变为可用到调用read返回数据之间存在时间间隔,会造成整体数据吞吐量降低
异步非阻塞
应用程序的其他处理任务与I/O任务重叠进行。读请求会立即返回,说明请求已经成功发起,应用程序不被阻塞,继续执行其它处理操作。当read响应到达,将数据拷贝到用户空间,产生信号或者执行一个基于线程回调函数完成I/O处理。应用程序不用在多个任务之间切换。
非阻塞I/O和异步I/O区别在于,在非阻塞I/O中,虽然进程大部分时间不会被block,但是需要不停的去主动check,并且当数据准备完成以后,也需要应用程序主动调用recvfrom将数据拷贝到用户空间;异步I/O则不同,就像是应用程序将整个I/O操作交给了内核完成,然后由内核发信号通知。期间应用程序不需要主动去检查I/O操作状态,也不需要主动从内核空间拷贝数据到用户空间。
非阻塞I/O看起来是non-blocking的,但是只是在内核数据没准备好时,当数据准备完成,recvfrom需要从内核空间拷贝到用户空间,这个时候其实是被block住的。而异步I/O是当进程发起I/O操作后,再不用主动去请求,知道内核数据准备好并发出信号通知,整个过程完全没有block。
文件描述符(基础概念补充)
文件描述符用于表示指向文件引用的抽象画概念。在形式上是一个非负整数,实际上是一个索引值,指向内核为每一个进程维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回个文件描述符。
Linux 系统中,包括文件、设备在内的许多事物的操作都被当成文件来操作,当 Linux 系统对文件操作时都会调用操作系统的系统调用,然后该系统调用返回一个文件描述符。在
Linux 系统中对网络 I/O 的操作同样也会返回相应的 Socket 文件描述符。
Linux 操作系统总共有 5 种网络 I/O 模型
l 阻塞I/O
l 非阻塞I/O
l I/O复用(select、poll、linux 2.6种改进的epoll)
l 信号驱动IO(SIGIO)
l 异步I/O(POSIX的aio_系列函数)
阻塞 I/O 模型
阻塞 I/O 模型是在操作系统发起系统调用调用之后,要等到操作系统系统内核所有的 I/O 操作完成才返回。阻塞 I/O 模型的内核态调用过程如下:首先操作系统内核调用 recvfrom()方法,调用之后进程进入阻塞状态,等待数据包达到。如果数据包到达或者在执行过程中出现 I/O 等方面的错误时才会调用完成,代码返回。阻塞 I/O 模型的特征主要是:当调用者使用阻塞 I/O 系统调用时,在 I/O 操作在内核态完成所有操作前,调用者会一直在这个点等待等待,处于阻塞状态;只有在操作系统内核完成了相应的操作之后函数才返回,调用者才能继续执行下面的代码。
非阻塞 I/O 模型是:进程使用非阻塞 I/O 系统调用时,如果系统由于繁忙等原因不能立即返回相应操作的结果,则该 I/O 函数会置相应的错误号并且立即返回,而不是和阻塞 I/O 操作一样,等待数据到来。非阻塞 I/O 模型的内核态调用过程如下:当调用 recvfrom()方法时,内核马上给该系统调用返回错误码。当再次调用recvfrom()方法时,如果操作系统的数据已经就绪,则会将数据复制到缓存区等待读取,同时 recvfrom()方法返回成功。如果操作系统的数据没有准备好,则继续返回错误码
I/O 复用模型
I/O 复用模型中,系统会首先构造一张有关文件或者 Socket 描述符的列表,然后调用一个特定的函数,当至少有一个描述符准备好进行 I/O 操作时,函数才会返回结果。此时进程就能够获取到可进行 I/O 操作的描述符集合。Linux 提供了select()/poll()接口来执行多路复用的功能
然而,select/poll 依次扫描文件描述符,依次判断文件描述符是否就绪。但是由于 select/poll 所能使用的文件描述符数量有限,因此它在实际使用过程中会有些限制。为了解决 select/poll 顺序扫描效率低下的问题,Linux 系统还有一种基于事件驱动方式的系统调用 epoll。由于 epoll 根据事件来查询文件描述符,因此性能会高很多。当有文件描述符的状态就绪时,模型马上执行之前传入的回调函数。
多路复用的本质是同步非阻塞I/O,多路复用的优势并不是单个连接处理的更快,而是在于能处理更多的连接。
I/O编程过程中,需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。
I/O多路复用技术通过把多个I/O的阻塞复用到同一个select阻塞上,一个进程监视多个描述符,一旦某个描述符就位,
能够通知程序进行读写操作。因为多路复用本质上是同步I/O,都需要应用程序在读写事件就绪后自己负责读写。
最大的优势是系统开销小,不需要创建和维护额外线程或进程。
- 应用场景
- 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字
- 需要同时处理多种网络协议的套接字
- 一个服务器处理多个服务或协议
目前支持多路复用的系统调用有select, poll, epoll。
信号驱动 I/O 模型
在信号驱动的 I/O 模型中,实现了真正意义上的异步形式的通知,通过信号机制来获取描述符的状态信息。首先在等待的描述符上注册回调函数,当事件发生后,回调函数负责将描述符状态写入用户空间并通知相关进程,对于某个描述符,
发生了所关心的事件。之后就可以在处理程序中调用 recvfrom()方法来读数据
异步 I/O
这种模型是真正意义上的异步。应用进程发起一个系统调用后,会马上返回,可以执行其他的操作。 但该调用会让内核触发一个操作,完成数据从内核拷贝到用户自己的缓冲区,操作完成后,内核会通知进程,然后进程对放在缓冲区的数据再进行处理。如图
同步IO和异步IO
- 同步IO操作导致请求进程阻塞,直到IO操作完成
- 异步IO操作不导致请求进行阻塞
从理论上讲,非阻塞IO、阻塞IO、IO复用和信号驱动IO都是同步IO模型。因为这四种IO模型中,IO的读写操作,都是在IO事件发生之后,由应用进程来完成的。而POSIX规范所定义的异步IO模型则不同。对异步IO而言,用户可以直接对IO执行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及IO操作完成之后内核通知应用程序的方式。异步IO的读写操作总是立即返回,而不论IO是否是阻塞的,因为真正的读写操作已经由内核接管。也就是说,同步IO模型要求用户代码自行执行IO操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区),而异步IO机制则由内核来执行IO操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的)。你可以这样认为,同步IO向应用程序通知的是IO就绪事件,而异步IO向应用程序通知的是IO完成事件。
资料来源:https://www.cnblogs.com/wuchanming/p/4442146.html作者:Jessica程序猿
链接:https://www.jianshu.com/p/439e8b349f48
來源:简书
https://blog.csdn.net/moakun/article/details/81042877作者:茅坤宝骏氹
论文:基于Netty的高可服务消息中间件的研究与实现_崔晓旻
基于Netty的消息中间件的研究与实现_夏斐
原文地址:https://www.cnblogs.com/cai-cai777/p/10222095.html