老张的火锅店最早实行“一桌一厨师”:每来一桌客人,就派一位厨师从头盯到尾。问题是,配菜要从后门送进来,厨师只能站在灶台前干等,一等就是好几分钟,锅里却什么都没有。客人越多,空等的厨师越多,厨房很快挤成沙丁鱼罐头。后来老张改了规矩:客人点完菜拿一个叫号器,配菜一到,叫号器“叮”地一响,厨师才开始动手。配菜还没到的单子,就安安静静夹在墙上的小夹子里,不占任何人。于是,10 个厨师轻轻松松扛下了以前 50 个厨师都忙不过来的生意。这个“叫号制”里藏着的,正是异步 I/O 的全部秘密:把等待从干活的人身上剥离出去,让有限的线程只处理“真正能动手”的任务。本文就带你走进这套机制——从同步阻塞的困境,到事件循环、回调、Future、协程,再到背压与改造选型,一步步还原这场并发领域的“火锅店革命”。
异步 I/O 是高并发后端的底层地基,也是 AI 推理服务承载海量流式请求的必备技能。掌握它,你就能用同样的机器扛住百倍流量,在架构评审与面试中游刃有余。
可视化演示
展示同步阻塞vs异步非阻塞IO对比、事件循环Reactor模式工作流程、协程async/await演进与背压处理。切换模式探索高并发IO核心原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
同步阻塞IO vs 异步非阻塞IO 同步IO每连接占一线程,线程阻塞等待IO完成,万级并发需万级线程 异步IO单线程事件循环管理所有连接,IO就绪后回调处理,1线程扛10K连接 sync: N_conn = N_thread; async: N_conn >> N_thread 异步IO是高并发服务的基石
同步IO之痛
老张火锅店的旧制度,咱们再仔细看一眼:后厨有 10 口灶,每来一桌客人都派一位厨师守着灶台,从点单、洗菜、等配菜,到最后下锅,全程不许离岗。最要命的是“等配菜”——货还没到,厨师只能站在空灶前发呆,锅是冷的,人是空的。10 桌要 10 位厨师干等,1000 桌呢?厨房里连下脚的地方都没有。
这个“一桌一厨师”就是同步阻塞 I/O 模型:一个网络连接分配一个线程,线程在等待数据到达的整个过程中被完全占用。网络请求有个显著特点——大部分时间都在“等”:等对端响应、等磁盘旋转、等数据库返回,真正花在计算上的时间往往不足 1%。也就是说,绝大部分线程都被“配菜没到”的假象困在空灶台前。
线程一点都不便宜:每个线程要分配独立调用栈,JVM 默认约 1MB;操作系统还要为它维护内核栈与调度信息。一万个连接对应一万个线程,光栈内存就接近 10GB;几万个线程同时跑来跑去,CPU 光顾着切换上下文,正经活反而没干多少。连接数一旦上万,系统最先被压垮的往往是线程本身。
回到火锅店:一桌一厨师的模式,本质是把“等待”和“做事”绑定在一个人身上。配菜没到,再多的厨师也只能干瞪眼。老张意识到,必须把“等菜”这件事从厨师身上拆出去——这正是异步 I/O 的起点。
- N同时存在的连接数,也就是线程总数。连接数上万时,这里的 N 就是灾难的开始。
- S单个线程占用的栈内存大小,JVM 默认约 1MB。
- M系统为线程分配的总内存开销。
- N × S总内存等于连接数与单线程栈的乘积,说明资源随连接数线性增长、毫无折扣。
异步核心思想
老张的新制度叫“叫号制”:点完菜,客人领走一个叫号器;配菜送到窗口,叫号器响一声;厨师听到哪个叫号器响,才过去做哪道菜。灶台永远只留给“食材已经就绪”的菜,等配菜这件事,交给了叫号器。厨师的双手,从此不再被等待绑架。
对应到技术就是两个关键动作:非阻塞调用——发起 I/O 的请求立刻返回,不傻等结果;就绪通知——数据可读可写时,由事件机制“叮”一声通知程序。Nginx、Netty、Node.js 都是这套思路的践行者:用少量线程,同时管理数万乃至数百万连接。
之所以能少线程扛大并发,是因为线程从不空等:等待被剥离到事件机制里,线程只处理“已经就绪”的任务。衡量线程干没白养,看利用率——单位时间里真正干活的时间占比。同步模型里这个数字常常只有 1%,异步模型在忙时能逼近 100%。
厨房还是那个厨房,厨师还是那十个人,改的只是“没人傻等”这条制度。叫号制的精髓,就是用“等的人”换成“等的机制”——异步 I/O 的核心思想,一句话:把等待从线程里剥离出去。
- T_busy单位时间内线程真正干活的时间,比如炒菜、计算、写响应。
- T_wait线程被阻塞、空等数据的时间。
- T_busy + T_wait线程被占用的总时长,同步模型里它俩是绑定的。
- U利用率,越接近 1 说明线程越没白养,异步的目标就是把它拉满。
事件循环机制
叫号制要落地,还差一个总调度——大堂经理。经理不停巡场:新客人来了?登记。哪个叫号器响了?记下“这道菜配菜好了”。厨师闲了?把就绪的单子派过去。经理一刻不停地转圈,但每一圈只处理“现在就能做的事”,绝不站在某一张桌子前死等。
这个“巡场的经理”就是事件循环:一个(或极少数)线程在循环里不停执行“取事件 → 分发给对应的处理逻辑 → 取下一个”。所有异步事件——网络数据到达、定时器到期、文件读完成——都被投进任务队列,等待被依次取出。
以 Node.js 的 libuv 为例,一轮循环分几个阶段:定时器回调、待处理回调、轮询 I/O 事件(阻塞等待新事件)、检查阶段、关闭回调。每个阶段都有独立的队列。这里有条铁律:每个事件的回调必须尽快返回,否则整个循环就像被客人拉住聊天的经理,全店都得陪等。
老张给经理定了规矩:巡场时只做“不耽误事”的动作,真正耗时的大活——比如颠勺炒菜——必须交给厨师。事件循环也一样:回调里别做重计算,重活交给线程池或异步化,这是事件循环高性能的前提。
- λ任务到达速率,即每秒有多少新事件进入队列。
- W每个任务在队列里平均等待被处理的时间。
- L队列中积压的任务数量。
- λ × W积压量由到达率和等待时间共同决定,两者任一失控,积压都会增长——这就是为什么回调不能慢。
回调模型
叫号制上了新台阶,但老张发现新问题:一道菜不是响一次铃就完事。配菜好了要炒,炒好要装盘,装完要上桌,上桌还要加汤。每完成一步,都得告诉下一个人“该你了”。于是每张订单都贴满便利贴,写着“菜好了叫我”“汤开了叫我”“买单了叫我”。
在代码里,“菜好了叫我”就是回调函数:把一个“完成之后要做什么”的函数,作为参数传给异步操作,操作完成后自动调用它。最早期的 Node.js 异步编程就是这个样子:fs.readFile(path, callback)——文件读完了,callback 被叫醒。
可一个真实业务流程往往要串起四五步异步操作,每步都要套一层回调,代码就成了层层缩进的“金字塔”。更糟的是,错误处理散落在每一层,参数在层与层之间传来传去,读代码的人几乎要窒息——这就是著名的“回调地狱”。
便利贴贴到第五张,连老张自己都看不懂了。他意识到:与其让每步都“喊人”,不如发明一个更高级的东西——叫号器。你来取号,机器替你记住“要做好几步”,每一步做完自动安排下一步。这个叫号器,就是 Future 与 Promise。
- T_i第 i 个独立异步任务的耗时。
- Σ T_i同步串行执行的总耗时,所有环节的时间累加。
- max(T_i)异步并行执行的总耗时,只取决于最慢的那个任务。
- 两种时间的对比任务越多、耗时越接近,并行相对串行的优势就越明显。
Future与Promise
老张的新发明:点完菜,服务员先递给你一个叫号器。你不用站在灶台前,该干嘛干嘛,菜好时叫号器会响。这个叫号器,就是 Future——一个“结果的占位符”:它现在就存在,但里面的值要等异步操作完成才会填上。你拿到的不是菜,而是“菜会来”的一个凭证。
Future 有明确的状态流转:初始是 Pending(进行中),成功变成 Fulfilled(已完成),失败变成 Rejected(已拒绝)。你可以在这个占位符上挂“成功做什么”“失败做什么”的处理器,甚至把它传来传去、组合复用——这是比回调便利贴高级得多的地方。
Promise 则把“接下来做什么”串成一条纵向的链:fetch(url).then(parse).then(render).catch(showError)。每一环都返回新的 Promise,嵌套的便利贴被拍平成一条流水线;任何一环出错,错误会沿着链一路滑落到 catch,统一处理。心智负担一下子降下来。
叫号器越来越多,老张却发现了更深一层的机会:客人不必自己站在窗口等铃响,服务员可以替客人“等着”,一个服务员同时照看几十个叫号器。从“占位符”到“有人替你等”,这就是协程的故事。
- p_i链上第 i 个环节的成功概率。
- ∏连乘符号,把所有环节的成功率乘在一起。
- P(success)整条 Promise 链全部成功的概率,例如 0.9×0.9×0.9≈0.73,三步就把成功率从 90% 拉到 73%。
协程演进
老张让服务员练就“一心多桌”的绝技:给 1 桌点完菜,在笔记本上画个暂停符——“到这里先挂起,菜好再回来”;转头去 2 桌继续点;1 桌的菜好了,翻回那一页接着往下办。一个人,照看十桌,靠的不是分身术,而是把“等待”拆成无数个可暂停、可恢复的小段。
这就是协程:一种用户态的轻量级执行单位,把“执行到一半挂起、稍后从断点恢复”做成语言特性。线程挂起要陷入内核,由操作系统切换;协程的挂起与恢复在用户态完成,成本低一两个数量级。创建十万个协程轻轻松松,创建十万个线程则会把系统压垮。
async/await 是协程的语法糖:await 一个异步操作时,函数自动挂起,把现场保存好,让出事件循环;操作完成后,从挂起点恢复执行。代码长得像同步一样直来直去,底层却全程非阻塞。Go 的 goroutine、Python 的 asyncio、Java 的虚拟线程,都是这个思路的产物。
服务员并没有长三头六臂,她只是把每一段“等菜”的间隙都填上了别的活。协程亦然:算力没有增加,但线程不再空转,同样的硬件扛住的并发自然翻了十倍百倍。
- C_thread一次线程上下文切换的耗时,约 1~2 微秒,需要陷入内核。
- C_coroutine一次协程挂起/恢复的耗时,约 40~100 纳秒,全程在用户态完成。
- R两者的比值,衡量协程在切换成本上的优势。
- 10~50比例的典型范围,意味着协程切换要快一两个数量级。
背压处理
生意火爆到极致,新烦恼来了:窗口 200 个叫号器同时响,厨房只有 3 口锅。配菜源源不断送进窗口,在备餐台上堆成小山,放不下,还容易放坏。老张下了死命令:备餐台只留 5 个格子,满了就通知前厅——先别接新单!
这就是背压(backpressure):当生产者的速度超过消费者时,必须主动限制流速,否则缓冲区的积压会撑爆内存。事件循环、流式处理、消息队列里到处是它的影子——有界队列、信号量限流、暂停/恢复数据流,都是背压的标准姿势。
积压的灾难可以用一个简单的式子算清楚:积压量 =(到达率 − 处理率)× 持续时间。只要到达率比处理率快一点点,哪怕只快 1%,积压也会随时间线性增长,最终内存溢出、服务雪崩。所以背压的本质不是“多堆点缓冲”,而是“让流速匹配”。
老张宁可少接几单,也绝不让窗口堆成山——背压的哲学正是如此:宁可拒绝,不可爆掉。一个健康的异步系统,永远把“流速可控”排在“吞吐最大”前面。
- λ任务到达率,每秒有多少新任务涌进来。
- μ任务处理率,每秒能消化多少任务。
- λ − μ净积压速度,大于 0 说明生产快于消费。
- Δt这种情况持续的时间。
- B累计积压量,随时间线性膨胀,最终撑爆缓冲区。
同步改造对比
隔壁炒饭快餐店老板来取经:“我们也改叫号制?”老张反问:“你们一锅炒饭 30 秒出锅,全程没人等配菜,改叫号制能省几个厨师?”快餐店老板想想,确实省不了。判断要不要改造,先看自己的业务像火锅店还是像快餐店。
同步模型直观好懂,调用栈完整,调试方便,适合低并发、CPU 密集、等待占比低的场景;异步模型心智负担高,错误栈会断,但等待不占线程,适合高并发、I/O 密集、等待占比高的场景。同步在连接数少时性能不错,连接一多就崩;异步恰恰相反。改造成本也不容忽视:语言生态里非阻塞库是否成熟、团队是否熟悉,都是要算的账。
收益可以提前估算:同步模型里每个连接占一个线程,线程大部分时间在等待;异步化之后,等待不再占用任何线程。若等待时间占总耗时的比例为 p,那么理论上单机可承载的连接数会提升约 1/(1−p) 倍——p 是 0.9 时提升 10 倍,p 是 0.99 时接近 100 倍。
老张的火锅店留下 10 个厨师,接下了 1000 桌的生意;隔壁快餐店继续一桌一锅,也活得滋润。异步 I/O 不是银弹,而是一把为“等待密集型”业务量身打造的利器。看清自己的等待占比,再决定要不要掀起这场革命。
- T_wait单个请求生命周期里花在等待 I/O 上的时间。
- T_total请求从发起到完成的全部耗时。
- p等待时间占总耗时的比例,I/O 密集业务常常高达 0.9~0.99。
- K异步化后单机可承载连接数的提升倍数,p=0.99 时接近 100 倍。
AI 应用场景
LLM 流式应答网关
阶段:推理服务的流量接入与部署
解决的问题:大模型生成 token 需要 5~30 秒,传统“一连接一线程”模型下每位用户都占住一个线程干等,1000 个并发就需要 1000 个线程,服务极易被打满。这正是火锅店“一桌一厨师”的老毛病。采用异步 I/O 的叫号制后,等待 token 的过程不占线程,单台 4 核机器即可挂住数万条 SSE 流式连接。
具体案例:某 AI 聊天机器人网关用 FastAPI + asyncio 重构:原来 8 台 4 核服务器扛 5000 并发,改造后 2 台就扛住了 2 万并发,且 CPU 占用率反而下降。
AI Agent 多工具并行编排
阶段:Agent 推理链路中的工具调用
解决的问题:Agent 每轮决策往往要同时调用多个外部工具——LLM 补全、向量库检索、网页搜索、数据库查询。串行执行总耗时是各步之和(比如 20 秒);这就像一道菜要等洗菜、切菜、配菜挨个排队。用 asyncio.gather 或 Future 组合让独立的调用同时等待,总耗时从“求和”变成“取最大”,延迟大幅下降。
具体案例:检索增强生成(RAG)管线中,文档召回、重排、摘要生成三段延迟约 0.4s、0.3s、2s;改为异步并行后,用户感知延迟从 2.7s 降到 2.1s,而系统线程数基本没变。
推理服务队列的背压保护
阶段:推理服务的稳定性保障
解决的问题:GPU 推理请求无界积压时,显存和内存双双爆掉,触发雪崩。这就像备餐台无限堆菜,最后连厨房门都堵死。借鉴背压思想,用有界队列 + 信号量限流 + 拒绝策略让到达率与处理率匹配,超载时快速失败并降级。
具体案例:某模型服务在高峰期改用有界队列(容量 512),超过后返回 503 并提示客户端退避重试,服务 P99 延迟从 8s 降到 1.2s,OOM 彻底消失。
Python 演示
import asyncioimport time async def fetch_one(task_id, latency): # 模拟一次网络 IO: 大部分时间在等待对端响应, CPU 空闲 await asyncio.sleep(latency) return "task-%d: done" % task_id async def async_main(tasks): limit = asyncio.Semaphore(3) # 背压: 最多 3 个并发 async def worker(task_id, latency): async with limit: return await fetch_one(task_id, latency) return await asyncio.gather(*(worker(tid, lat) for tid, lat in tasks)) def sync_main(tasks): for tid, latency in tasks: time.sleep(latency) # 同步阻塞等待, 线程被占死 print("task-%d: done" % tid) if __name__ == "__main__": tasks = [(1, 0.2), (2, 0.3), (3, 0.1), (4, 0.4), (5, 0.2), (6, 0.3)] t0 = time.perf_counter() sync_main(tasks) t1 = time.perf_counter() res = asyncio.run(async_main(tasks)) t2 = time.perf_counter() print("sync : %.3f s" % (t1 - t0)) print("async: %.3f s" % (t2 - t1)) print("async faster: %.1fx" % ((t1 - t0) / (t2 - t1))) print(res)
async def fetch_one(task_id, latency):定义协程函数,调用时不会立即执行,而是返回一个协程对象。await asyncio.sleep(latency)挂起点:把控制权交还事件循环,模拟“等网络数据”时不占线程。limit = asyncio.Semaphore(3)背压:只允许 3 个任务同时占用资源,其余自动排队。async with limit:进入信号量,超过 3 个并发时在此等待,这就是有界并发。await asyncio.gather(*(worker(tid, lat) for tid, lat in tasks))并行发起所有任务,总耗时约等于最慢的一个。time.sleep(latency)同步版本:线程被阻塞,等待期间什么也干不了。asyncio.run(async_main(tasks))创建并驱动事件循环,运行整个协程。
常见误区
误区我以为异步 IO 就是开更多线程去跑,线程越多性能一定越好,所以异步系统肯定比多线程同步系统快得多。
正解异步和线程是两个维度。异步并不增加 CPU 算力,核心收益是“等待不占线程”。如果业务是纯 CPU 计算,异步反而会因为调度开销略慢。火锅店的叫号制没有让厨师炒菜更快,只是不让厨师在配菜没到时干等。判断快慢的指标是等待占比,而不是线程数量。
为何常见:异步系统往往和多线程、高并发一起出现,初学者容易把“手段”当成“结果”。
误区把函数加上 async 关键字,或者用 asyncio 重写一遍,就以为同步业务自动变成高并发,性能立刻翻倍。
正解async/await 只是语法糖,底层必须配合真正的非阻塞 I/O(epoll、io_uring、kqueue)。如果里面调的还是阻塞的 requests.get 或同步数据库驱动,事件循环会被“假睡”的代码卡死,性能甚至更差。就像服务员嘴上说同时服务十桌,但每桌都死等 5 分钟菜,等于没服务。
为何常见:框架宣传“高并发”光环,让开发者误以为语法等于性能,忽略了底层 I/O 模型的匹配。
误区回调地狱是 Node.js 的专利,我用 Java 或 Python,就不会遇到层层嵌套的问题。
正解任何采用回调式异步的语言都会遇到回调地狱:Java 的 Netty、C++ 的 libevent、Python 早期的 Twisted,都能写出同样的金字塔。Promise 和协程是跨语言的解法演进,而不是某个语言的原生优势。老张的便利贴,在任何一家用“菜好了叫我”制度的火锅店都会贴满墙。
为何常见:前端面试总拿 JavaScript 举例,容易让人产生幸存者偏差,以为只有 JS 异步才这么麻烦。
误区事件循环是单线程的,不存在线程安全的问题,异步代码里随便共享状态也不会出错。
正解单线程只是避免了“两个线程同时改同一块内存”的传统竞态,但异步代码存在“等待期间状态被其他事件悄悄改掉”的问题,回调之间共享的变量仍然要小心。真实系统还常常是事件循环 + 线程池的组合。一个经理不会同时接两个电话,但他记的台账可能被别的服务员改掉。
为何常见:把“单线程”误解为“无并发”,忽略了异步世界中“交错执行”才是常态。
误区消费者处理得慢,那就多开点缓冲,把消息都堆起来慢慢消费,反正内存够大。
正解无界缓冲只是把崩溃推迟。由 B=(λ−μ)×Δt 可知,生产快于消费时积压会线性增长,最终内存爆掉或数据过期。正确的做法是有界队列配合限流、拒绝和降级,让系统优雅地处理过载,而不是硬扛。老张宁可让备餐台少放几格,也不让菜堆到变质。
为何常见:“先存起来”最符合直觉,看似无害,实际上只是把灾难从眼前推迟到下一刻。
面试题
Q1. 传统“一连接一线程”的同步模型,连接数变大后最先撞上的瓶颈是什么?
- A.线程栈内存与切换开销暴涨← 正确答案
- B.磁盘空间不足导致无法写入
- C.网卡带宽不足导致传输变慢
- D.数据库连接池容量不够用
答案:A。同步“一连接一线程”模型下,每个连接都要占用一个线程的栈内存(JVM 默认约 1MB),10 万连接就是约 100GB 内存,且大量线程来回切换会耗尽 CPU。磁盘、网卡、数据库都能横向扩容,但线程的内存与切换开销是模型本身的结构性瓶颈。
Q2. 异步 IO 中“非阻塞调用”的含义是什么?
- A.程序无需等待任何结果返回
- B.数据在传输过程中永不丢失
- C.调用立即返回且就绪后通知← 正确答案
- D.操作系统可随时暂停当前程序
答案:C。非阻塞 IO 的调用会立即返回,不等待数据到达;数据就绪后由事件机制(如 epoll)通知程序再处理。A 是“非阻塞”的过度解读,B 是可靠性问题,D 是操作系统调度,都不属于非阻塞调用的定义。就像叫号器:你先拿走,菜好才响铃。
Q3. 下列哪种业务最适合改造成异步 IO?
- A.纯 CPU 密集计算为主的业务
- B.单次本地加法计算为主的业务
- C.大量网络读写等待为主的业务← 正确答案
- D.低频定时任务触发为主的业务
答案:C。异步 IO 的收益来自“等待不占线程”,因此等待占比越高的场景收益越大。大量网络读写正是典型的高等待场景;纯计算、单次加法、低频定时任务几乎不等待,改造异步没有意义甚至更慢。火锅店里只有“配菜要等很久”的店才需要叫号制。
Q4. 在事件循环中,已就绪的 IO 回调在什么时机执行?
- A.注册回调函数的瞬间就立刻执行
- B.同步代码跑完后的下一个循环轮次← 正确答案
- C.由操作系统随机安排某个时间执行
- D.在独立的其他线程中并行执行
答案:B。事件循环是单线程的,同步代码必须完整执行完,回调才会在后续的循环轮次中被取出执行。A 错在异步回调不可能“立即”执行;C 错在事件循环按队列顺序而非随机;D 错在回调默认在事件循环线程执行,不是新线程。经理巡场总得先把手头这桌的事忙完。
Q5. 相比层层回调,Promise 的核心改进体现在哪些方面?
- A.把嵌套的调用拍平成链式← 正确答案
- B.让错误沿着链条统一传递处理← 正确答案
- C.每个阶段都能拿到并处理中间结果← 正确答案
- D.执行时不再依赖底层的事件循环
答案:ABC。Promise 的核心改进是把回调的横向嵌套“拍平”为纵向链式,错误沿链统一传递给 catch,每个 then 还能拿到上一步的中间结果。D 不对:Promise 本身就是事件循环调度的产物,不可能脱离事件循环。“叫号器”只是让取菜变得清晰,厨房(事件循环)仍要运转。
Q6. 下列哪些是背压处理的常见实现手段?
- A.使用有界队列对任务进行限流← 正确答案
- B.通过信号量控制并发任务的个数← 正确答案
- C.无限增加缓冲区以吸收全部压力
- D.提供暂停与恢复数据流的控制接口← 正确答案
答案:ABD。背压的目标是让消费速度跟上生产速度:有界队列(放不下就拒绝/等待)、信号量(限制并发数)、暂停/恢复流(TCP 窗口、流的 pause/resume)都是标准做法。C 恰恰相反——无限缓冲只是把崩溃推迟,积压会随时间线性增长最终内存爆掉。
Q7. 协程切换比线程切换开销低,根本原因是什么?
- A.协程切换时无需保存任何寄存器现场
- B.切换在用户态完成且不经过操作系统内核← 正确答案
- C.协程本身不占用系统里的任何内存资源
- D.协程运行时只允许在单核 CPU 上执行
答案:B。线程切换要陷入内核、保存/恢复完整上下文,耗时约 1~2 微秒;协程切换在用户态完成,只需保存少量现场,约几十纳秒,快一两个数量级。A 错在协程也要保存现场(只是更轻量);C 错在协程也占内存(远小于线程栈);D 显然错误。
Q8. 设任务到达率 λ=200 请求/秒,平均等待 W=0.5 秒,按 L=λ×W 计算,队列积压任务数约为多少?
- A.约 50 个任务
- B.约 100 个任务← 正确答案
- C.约 200 个任务
- D.约 400 个任务
答案:B。Little's Law 直接代入:L = λ × W = 200 × 0.5 = 100 个任务。这是背压问题的数学本质:队列积压由到达率和平均等待时间共同决定,任何一端失控都会导致积压爆炸。备餐台上的菜,取决于配菜送来的速度和做菜的速度。
Q9. 系统出现“生产快于消费”的持续过载时,正确处理方式是什么?
- A.无限扩大缓冲容量以避免数据丢失
- B.适当提高生产者的批次发送大小
- C.关闭消费者的失败重试机制以减少压力
- D.使用有界队列配合限流与优雅降级← 正确答案
答案:D。生产快于消费时,正确姿势是“有界 + 限流 + 降级”:让上游感知下游压力并主动降速或拒绝。A 的无限缓冲会把灾难推迟而不是消除;B 只改单侧参数;C 关闭重试只是掩盖问题,它们都没解决“流速不匹配”的根本矛盾。老张的选择是:备餐台满了就先别接单。
Q10. 关于 async/await 与协程,下列说法正确的是?
- A.底层仍依赖事件循环或协程调度器← 正确答案
- B.只有配合非阻塞 IO 才有明显收益← 正确答案
- C.代码能保持近似同步的线性写法← 正确答案
- D.使用之后程序运行速度必然变得更快
答案:ABC。async/await 是协程的语法糖,底层仍是事件循环或调度器;只有底层 IO 是非阻塞的,挂起才有意义;它最大的价值是让异步代码保持近似同步的可读性。D 错:如果底层是阻塞 IO 或任务是 CPU 密集,加了 async/await 不仅不变快,反而可能变慢——叫号制救不了本来就不需要等菜的快餐店。