同步/异步、阻塞/非阻塞有什么区别?
简化版
这是两个不同维度,别混。阻塞/非阻塞关注「发起调用后要不要挂起等待」——阻塞是傻等到有结果,非阻塞是立即返回(没结果就返回一个「还没好」)。同步/异步关注「这件事最终由谁完成、结果怎么通知你」——同步是你自己发起并等/查结果,异步是交给别人做、做完了主动通知你。所以「非阻塞」不等于「异步」,两者可以组合出四种情况。
详细版
阻塞 vs 非阻塞(关注调用发起方的状态):
- 阻塞:发起调用后,如果结果没准备好,当前线程被挂起,啥也干不了,直到有结果才返回;
- 非阻塞:发起调用后立即返回——有结果给结果,没结果就返回一个「暂时没好」的标志,线程可以继续干别的(但要自己再来问)。
同步 vs 异步(关注任务由谁完成、如何得知结果):
- 同步:你自己发起这件事,并且自己关心/等待它的完成(无论是阻塞地等,还是非阻塞地反复轮询);
- 异步:你把事委托出去,自己不管了去干别的,等它完成后由对方主动通知你(回调、通知、事件)。
四种组合(用「打电话订餐」类比):
| 阻塞 | 非阻塞 | |
|---|---|---|
| 同步 | 打电话点餐,一直握着电话等做好(阻塞等) | 打完电话挂了,每隔几分钟打回去问好没好(轮询) |
| 异步 | (少见)委托别人办,自己却干等结果 | 点餐时留电话,做好了店家打给你(回调通知) |
完整版教学
一、为什么要分成两个维度
初学者最大的困惑是把「同步/异步」和「阻塞/非阻塞」当成一回事,其实它们回答的是两个不同的问题:
- 阻塞/非阻塞回答:「我发起调用后,线程会不会被卡住干等?」——描述的是调用方等待时的状态;
- 同步/异步回答:「这件事是我自己盯着做完,还是交出去等通知?」——描述的是消息通知/协作的机制。
一个说的是「等的方式」,一个说的是「谁负责完成、怎么告知」。两个维度正交,所以能组合出四种情况。分清这两个问题,就不会再纠结了。
二、用「订餐」类比彻底理解
抽象概念用生活场景最好懂。你去餐厅点餐(发起 IO),等菜做好(IO 完成):
- 同步阻塞:你在窗口点完餐,一直站在窗口干等,直到菜做好递给你。期间你啥也干不了。(对应阻塞 IO)
- 同步非阻塞:你点完餐去旁边坐着,但每隔一会儿就跑回窗口问「我的菜好了吗」——不傻等,但得自己反复来问(轮询)。(对应非阻塞 IO / 多路复用)
- 异步(非阻塞):你点餐时留个手机号/取餐器,然后安心去玩,菜做好了店家打电话/取餐器震动通知你。你完全不用管、也不用问,做好了自然找你。(对应异步 IO)
关键差异:同步是「你要自己关心结果」(等或问),异步是「做好了会主动通知你」。 阻塞是「站着等」,非阻塞是「不站着等(但可能要自己再来问)」。
三、把 IO 模型对号入座
这套概念的经典应用就是解释五种 IO 模型(详见「五种 IO 模型」那道题):
- 阻塞 IO = 同步 + 阻塞:调
recvfrom一直等到数据好并拷贝完; - 非阻塞 IO = 同步 + 非阻塞:
recvfrom立即返回,自己轮询问好没好; - IO 多路复用 = 同步 + (阻塞在 select 上):用 select/epoll 统一等就绪,但拷贝还得自己同步做;
- 异步 IO(AIO) = 异步:发起后不管了,内核把「等 + 拷贝」全做完再通知你。
注意:前三种都是同步(数据拷贝那一步要你自己参与/等待),只有 AIO 是异步。所以多路复用虽然高效,但仍是同步的——这是高频考点。
四、最容易搞错的一点:非阻塞 ≠ 异步
这是这道题的核心易错点,务必记牢:
- 非阻塞只是说「发起调用后线程不挂起等待,立即返回」——但你还得自己再来问结果(轮询),结果仍是你自己关心、自己取;
- 异步是「交出去后彻底不管,做完了对方通知我」——结果由对方送上门。
所以非阻塞 IO 里,你虽然不阻塞,但要不停轮询,本质还是同步(自己盯着结果)。真正的异步是「留个回调,做完了叫我」。别把「不阻塞」和「异步」画等号。
五、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 同步/异步 | 关注结果由谁推动完成、调用方是否继续参与 |
| 阻塞/非阻塞 | 关注调用线程是否等待被挂起 |
| 常见组合 | 同步阻塞、同步非阻塞、异步非阻塞 |
blocking: call read() -> thread sleeps until data
nonblocking: call read() -> EAGAIN if no data
async: submit read -> later callback/future gets result
同步/异步看“结果机制”,阻塞/非阻塞看“线程状态”,这是两条维度,别混成一条。
- 误区:同步一定阻塞。 同步调用也可以非阻塞返回 EAGAIN,由调用方稍后再试。
- 误区:非阻塞就是异步。 非阻塞只是立即返回,异步强调操作完成后通过回调、事件或 future 通知。
- 误区:异步一定更快。 异步提高资源利用率和并发能力,但单个操作的物理耗时不一定变短。
- 追问:select/epoll 属于同步还是异步? 通常认为是同步 IO 多路复用:应用等待就绪后仍自己执行 read/write。
- 追问:回调和 Future 解决什么? 它们是异步结果通知/获取方式,避免调用线程一直等待。
- 追问:为什么概念容易混? 很多框架把非阻塞事件循环封装成异步 API,但底层语义仍要拆开看。
六、加强记忆
阻塞/非阻塞和同步/异步是两个正交维度:阻塞/非阻塞看「发起调用后线程要不要挂起干等」(阻塞=站着等,非阻塞=不站着等但要自己再问);同步/异步看「结果由谁负责、怎么告知」(同步=自己发起并关心/等待结果,异步=委托出去、做完主动通知你)。用订餐类比:同步阻塞=窗口干等,同步非阻塞=反复回来问,异步=留电话做好了叫你。核心记牢:非阻塞 ≠ 异步,IO 多路复用是同步的。