在学习高并发编程的时候,在搞清楚并发编程中使用多线程、多进程和 I/O复用的区别时,发现自己对阻塞、非阻塞和同步、异步理解还不是很了解。所以本节是针对阻塞、非阻塞和同步、异步的学习。
先举例说明
下面来自知乎上面的一个回答:
老张爱喝茶,废话不说,煮开水。出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)(PS:时不时去看看和看电视可以是2个线程或进程来负责)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
尽管这个例子不是特别准确,但是对于初学者来理解阻塞、非阻塞和同步、异步是很好的。
详细说明
5种类UNIX下可用的I/O模型:
-
阻塞式I/O;
-
非阻塞式I/O;
-
I/O复用(select,poll,epoll...);
-
信号驱动式I/O(SIGIO);
-
异步I/O(POSIX的aio_系列函数);
阻塞式I/O模型:默认情况下,所有套接字都是阻塞的。怎么理解?先理解这么个流程,一个输入操作通常包括两个不同阶段:
- 等待数据准备好;
- 从内核向进程复制数据。
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。 好,下面我们以阻塞套接字的recvfrom的的调用图来说明阻塞。
标红的这部分过程就是阻塞,直到阻塞结束recvfrom才能返回。
非阻塞式I/O: 以下这句话很重要:进程把一个接字套设置成非阻塞是在通知内核,当所请求的I/O操作非得把本进程投入睡眠才能完成时(PS:说明已经进入“将数据从内核拷贝到用户空间“这个过程),不要把进程投入睡眠,而是返回一个错误。看看非阻塞的套接字的recvfrom操作如何进行。
可以看出recvfrom总是立即返回。
I/O多路复用:虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。如图
信号驱动式I/O:用的很少,就不做讲解了。直接上图
异步I/O:这类函数的工作机制是告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到用户空间)完成后通知我们。如图:
注意红线标记处说明在调用时就可以立马返回,等函数操作完成会通知我们。
等等,大家一定要问了,同步这个概念你怎么没涉及啊?别急,您先看总结。 其实前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。相反,异步I/O模型在这两个阶段都要处理。
再看POSIX对这两个术语的定义:
-
同步I/O操作:导致请求进程阻塞,直到I/O操作完成;
-
异步I/O操作:不导致请求进程阻塞。
好,下面我用我的语言来总结一下阻塞,非阻塞,同步,异步
-
阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;(PS:第一阶段)
-
同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。(PS:第二阶段)
参考原文: