1、异步编程基础概念:同步与异步、阻塞与非阻塞
说实话,这章看起来基础,但我见过不少做SDK集成的同事,就是在这四个词上栽了跟头。你想想看,如果连同步异步都分不清,后面做回调改造时会迷糊。
我个人习惯,讲这组概念时喜欢从生活的例子切入。等弄明白了,再往代码里套。
1.1 同步 vs 异步:一个打电话,一个发微信
什么叫同步?说白了就是你给客服打电话。
- 你问问题,对方回答之前,你只能举着手机等着。
- 你动不了,电话不挂,你就干不了别的。
- 这就是典型的同步模型。发起请求后,必须等结果返回,才能继续往下走。
那异步呢?更像发微信。
- 你把消息发出去,不用盯着屏幕等回复。
- 你可以刷朋友圈、回邮件、点外卖。
- 等对方回你消息时,手机"叮"一声通知你。
- 这就是异步。发起请求后不阻塞当前流程,等结果好了再通过某种方式通知你。
我在项目中遇到过一位刚转Python的后端同学,他把SDK里的异步接口当同步调,结果主线程直接卡死。后来我让他换了个思路——把每次调用想象成给服务器发了条微信,你该干嘛干嘛,等回调来了再处理。
1.2 阻塞 vs 非阻塞:你在等,还是不等?
阻塞和非阻塞,强调的是调用方的状态。
阻塞:调用一个函数后,这个函数没返回,你就卡在那儿。什么事都做不了,CPU干等着。
// 典型阻塞调用
data = socket.recv(1024) # 没数据来,这行代码就卡死在这里
print("这行代码只有在收到数据后才能执行")
非阻塞:调用后立刻返回,不论结果是否就绪。没数据?那返回一个空或者抛个"再试一次"的错误。
# 设置非阻塞模式
socket.setblocking(False)
try:
data = socket.recv(1024)
except BlockingIOError:
print("没数据,我先干别的去了")
我记得有一次帮同事排查SDK超时问题。他的代码里用了一个阻塞读,网络稍微一抖,整个UI就死了。后来换成非阻塞加轮询,体验好很多。
1.3 四种组合:一张表说清楚
同步和异步说的是交互方式,阻塞和非阻塞说的是调用者状态。两两组合,正好四种情况。
| 组合 | 说明 | 举例 |
|---|---|---|
| 同步阻塞 | 发起调用,等结果,期间啥也不干 | 早期Java的BIO,一条连接一个线程 |
| 同步非阻塞 | 发起调用,立刻返回,但得自己反复检查结果 | 轮询模式,比如select/poll |
| 异步阻塞 | 等通知,但等待方式是阻塞的 | Future.get() 设了超时,在那等着结果 |
| 异步非阻塞 | 发起调用就走,结果好了被通知,期间完全自由 | 回调函数、Python asyncio、Node.js |
这里面最理想的是异步非阻塞。SDK集成里的回调改造,最终目标就是让调用方处于这种状态——发起请求后该干嘛干嘛,等SDK处理完了,通过回调把结果送回来。
1.4 为什么这个基础对SDK改造这么重要?
你可能在想:我就改个回调接口,搞清楚这些能干嘛?
用处大了。
- 你对接的第三方SDK,提供的接口可能是同步阻塞的。你想让它变异步,就得先知道它在阻塞什么。
- 你写的回调函数,如果在里面做了阻塞操作,那本质上又把异步变成了同步。
- 你设计的回调接口,到底是传结果还是传异常?这取决于你对异步模型的理解。
嗯,我曾经见过一个团队,在回调函数里写了一个sleep(10)。结果整个异步框架的线程池被堵死,连心跳包都发不出去。事后复盘,根源就是没搞清楚"阻塞"在异步模型中的危害。
1.5 一张图看懂核心关系
下面这张SVG图,把同步异步、阻塞非阻塞的四种状态,以及它们和回调机制的关系,画在了一起。建议你保存下来,后面每章都回来看一眼。
这章的脉络就是这样了。掌握了同步异步、阻塞非阻塞这组概念,后面讲回调接口设计、线程模型、事件循环,你才能跟上节奏。