本篇文章我准本从三个大方面来解释下同步异步、阻塞非阻塞的知识,第一个方面主要是说下,到底什么是同步异步、阻塞非阻塞;第二个方面主要是解释下在I/O场景下,同步异步阻塞非阻塞又是怎么定义的,第三个方面介绍下在unix下同步异步又有哪些阻塞非阻塞IO。
1、同步异步与阻塞非阻塞
首先从大的方面来说,“阻塞”与"非阻塞"与"同步"与“异步"不能简单的从字面理解,提供一个从分布式系统角度的回答。
1).同步与异步
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。
而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。
典型的异步编程模型比如Node.js
举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
2). 阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
2、在IO情况下的同步异步与阻塞非阻塞
关于同步异步、阻塞非阻塞的概念: 同步就是同步,异步就是异步! 只有同步时才有阻塞和非阻塞的实现,而对于异步虽说可以有异步阻塞和异步非阻塞,实现上却很少。关于这些概念之间的关系的理解,可以通过以下几个例子来说明。
例子1:老板布置了一个调研任务,老板自己需要写一个调研报告。这个调研的一个子任务由同学A完成,老板最终需要将同学A的任务整合到自己的调研报告中。因为老板的调研报告需要用到同学A的调研结果,那么老板的调研报告就必须等到同学A调研完成以后才能完成。那么在整个调研的过程中,最终的调研报告和同学A的调研任务之间就是同步关系,因为两个任务之间有着时序的关系。
例子2:老板又布置了一个调研任务,但是这个调研任务是可行性调研,不需要写报告,只需要知道可不可行即可。这个任务由一个同学B来调研;同学B调研完成之后,直接将结果反馈给老板即可。那么在这个调研过程中,老板做其他的事情去了,而同学B在做调研工作。在此过程中,老板做的事情和同学B做的事情之间就是异步关系,因为老板做的事情和同学B做的事情没有关系,并行完成。
例子3:延续例子1中的事情,假如需要A同学完成的事情很简单,那么老板就可能会选择,站在A旁边等到A调研结束,再进行剩余的工作。那么此时,老板就处于阻塞状态。
例子4:同样是例子1,如果同学A要做的事情很浪费时间,需要几天才能完成,这时老板自然不会傻X的站在旁边等着。这个时候,老板可能就会选择做其他的事情,但是由于报告还是需要完成的,老板也许隔段时间就会的问,好了没有啊,你好了没有啊之类的。等到同学A调研完成以后,继续进行调研报告的撰写工作。这个过程中呢,老板呢就是处于非阻塞状态,因为他在做其他的事情,并没有一直在等待同学A。
总得来说,同步异步针对的是两个事情之间的关系,这种关系与拓扑结构中的先后关系类似类,如果两件事情之间存在拓扑关系,便是同步关系;如果没有时序先后的关联或相互依赖,则是异步关系。而阻塞非阻塞针对的是发起任务的人(线程)的状态问题。在I/O中,同步异步涉及的两件事情是:消息响应和消息处理,I/O模型中这两个事务之间的拓扑关系决定了模型是同步还是异步的。而在消息响应的过程中,发出I/O请求的线程所处的状态决定了模型是阻塞还是非阻塞的。
3、unix环境下、关于IO同步异步阻塞非阻塞的阐述
《UNIX网络编程:卷一》第六章——I/O复用。书中向我们提及了5种类UNIX下可用的I/O模型:
- 阻塞式I/O;
- 非阻塞式I/O;
- I/O复用(select,poll,epoll...);
- 信号驱动式I/O(SIGIO);
- 异步I/O(POSIX的aio_系列函数);
1)阻塞式I/O模型:默认情况下,所有套接字都是阻塞的。怎么理解?先理解这么个流程,一个输入操作通常包括两个不同阶段:
(1)等待数据准备好;
(2)从内核向进程复制数据。
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。 好,下面我们以阻塞套接字的recvfrom的的调用图来说明阻塞
标红的这部分过程就是阻塞,直到阻塞结束recvfrom才能返回。
2)非阻塞式I/O: 以下这句话很重要:进程把一个套接字设置成非阻塞是在通知内核,当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误。看看非阻塞的套接字的recvfrom操作如何进行
可以看出recvfrom总是立即返回。
3)I/O多路复用:虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调 用如recvfrom之上
4)信号驱动式I/O:用的很少,就不做讲解了。直接上图
5)异步I/O:这类函数的工作机制是告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到用户空间)完成后通知我们。如图:
注意红线标记处说明在调用时就可以立马返回,等函数操作完成会通知我们。
等等,大家一定要问了,同步这个概念你怎么没涉及啊?别急,您先看总结。 其实前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。相反,异步I/O模型在这两个阶段都要处理。
再看POSIX对这两个术语的定义:
- 同步I/O操作:导致请求进程阻塞,直到I/O操作完成;
- 异步I/O操作:不导致请求进程阻塞。
好,下面我用我的语言来总结一下阻塞,非阻塞,同步,异步
- 阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;
- 同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。