阻塞、非阻塞、同步、异步的区别是什么?
同步异步描述的是被调用方。阻塞非阻塞描述的是调用方。二者没有必然联系。
- 阻塞是调用方A发出命令后,必须等待B返回结果。非阻塞是调用方A发出命令后,A不需要等待B,可以做自己的事情。
- 同步是B收到A的指令之后会立即执行,A可以得到结果。异步是B收到A的指令之后不会立即执行要做的事情,A的本次调用不会得到结果,但是B执行完要做的事情之后会通知A。
我们常常混淆的同步异步、阻塞非阻塞其实是放在特定场景下的,不能一概而论,IO也分为磁盘IO和网络IO。这里所讲的IO一般指网络IO。
- 阻塞IO和非阻塞IO:指的是socket编程中发起read函数系统调用读取数据后是否阻塞住
- 如果一直等待到有数据才返回,这个read就是阻塞的,也是同步的
- 如果没有数据就返回-1而不是等待,这个read就是非阻塞的,也是同步的
- 同步IO和异步IO:指的操作系统内核是否自动将数据从内核空间拷贝到用户空间
- 如果需要read函数自己将数据拷贝到用户空间就是同步IO
- 如果内核自动将数据拷贝到用户空间,并且通知用户,就是异步IO(一般在Linux上用的少,windows有完整实现)
- 同步执行和异步执行:指的是是否按照代码编写的顺序执行
- 如果按照代码位置顺序执行,就是同步执行
- 如果像js里面的setTimeout,设置了callback之后,执行时候直接执行后面的。到达设定时间才开始执行前面设定的callback函数。就是异步执行。
- 同步调用和异步调用:指的是是否通过多线程调用函数
- 如果只在单线程内顺序执行方法就是同步调用
- 如果新建线程执行方法就是异步调用
- 同步请求和异步请求:指的是前端发起的是整体页面请求还是局部请求
- 如果是整个网页的请求那就是同步请求
- 如果是ajax这种局部请求,那就是异步请求,一般会定义回调函数用于接收响应后的处理。
网络IO演进分析
同步阻塞网络IO
最开始学习基础Linux的Socket编程时候,流程如下:
1 | // 伪代码 |
客户端和服务端具体的交互流程如下,其中服务端的accept 函数和read 函数都会阻塞,前者等待三次握手结束才会向下执行,后者等待数据就绪才会向下执行(参考小林coding):

这里主要关注read函数,看下图可以知道会一直阻塞直到数据从网卡拷贝到内核缓冲区完成。read才开始执行将数据从内核空间拷贝到用户空间(参考低并发编程):

整个同步阻塞IO的流程如下:
上面同步阻塞IO会导致客户端一直阻塞,新客户端连接也不能处理。一种解决办法就是新建一个线程来处理客户端的请求:
1 | while(1) { |
扩展知识:进程和socket在内核中的关系:
同步非阻塞网络IO
使用多线程可以避免一个人阻塞其他人,但是效率还是比较低。在发起系统调用read的时候,依旧是阻塞的。而且多线程在用户多时会导致大量线程创建,不是明智之举。所以需要在操作系统层面提供支持,也就是提供非阻塞的 read 函数:

实现的时候就将客户端创建的连接都放到一个集合里面,然后对集合里面的连接不断执行read直到读取到数据:
1 | // 伪代码 |
整个同步非阻塞IO的流程如下:
总结
同步阻塞和非阻塞都是按照写的代码的顺序执行的,只是后者立即返回错误结果,需要代码层面不断轮询直到拿到结果。
但是不断轮询当前所有的连接是否有数据就绪也不是好的办法,因为每一次轮询都是一次系统调用,开销还是比较大的,所以操作系统实现了 IO多路复用 这一利器避免轮询。我是deltaqin,下篇介绍IO多路复用!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 褚成志的分享站!
