想象一家繁忙的餐厅。主厨(主线程)是唯一能把菜端到顾客面前的人——浏览器里也只有他能更新页面。某天他接到一道需要慢炖三小时的硬菜(一次大模型推理),于是放下手头所有工作专心炖汤,顾客们(鼠标点击、页面滚动、动画播放)只能在门口干等——这就是你看到的“页面卡死”。聪明的餐厅老板请来了帮厨(Web Worker):帮厨可以在后厨另起一个灶台,慢炖菜全部交给他们,主厨继续流水出菜。不过两边之间只有一个小小的传菜口(postMessage),递菜要复印菜谱(结构化克隆),端大锅就整锅转移(Transferable),一整头牛得切块分批炖(任务分片),炖好一盘就先端出一盘(结果回传)……本文就用这家“并行餐厅”的故事,为你讲透 Web Workers 如何让 AI 推理既不卡界面、又能跑得飞快。

岗位关联

掌握这套知识,你就能在不阻塞主线程的前提下跑通浏览器端 AI 推理,让复杂的模型在用户手机里“隐形”工作——这是每个前端 AI 工程师绕不开的硬核基本功。

目录
  1. 可视化演示
  2. 主线程阻塞
  3. Worker原理
  4. 通信机制
  5. 数据传输
  6. 任务分片
  7. 结果回传
  8. 模型转移
  9. 工程落地
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

演示Web Workers中主线程与工作线程的通信、模型转移与任务分片推理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Worker通信 主线程 Worker post→msg 主线程与Worker消息通信

主线程阻塞

在餐厅里,主厨是唯一能把菜端上桌的人;在浏览器里,主线程是唯一能更新页面的角色。一次 AI 推理不是“顺手撒把盐”的小事,而是“慢炖三小时的老火汤”。当这段计算在主线程上执行,事件循环就被它牢牢占住:点击排队、滚动排队、动画排队,整个餐厅陷入静止。

浏览器通常每 16.7 毫秒渲染一帧画面(60fps),而一次推理往往要跑几十到几百毫秒。推理任务一启动,一帧又一帧的渲染预算被挤爆,用户看到的就是白屏卡死、按钮没反应。这个问题的本质,是“炖汤”与“出菜”抢了同一个灶台。

结论很直接:只要“慢炖的推理”还占着主厨的灶台,界面就注定卡顿。所以我们要把这道硬菜从主厨手里拿走——这正是 Web Worker 登场的理由。主厨专心出菜,帮厨负责炖汤。

Tframe=Trender+Ttask\displaystyle T_{\text{frame}} = T_{\text{render}} + T_{\text{task}}
一帧画面的时间 = 渲染时间 + 主线程任务时间。任务时间一长,帧时间超预算,画面就卡。
  1. 一帧总耗时$T_{\text{frame}}$ 表示浏览器完成一帧画面渲染所花的全部时间。
    Tframe\displaystyle T_{\text{frame}}
  2. 渲染开销$T_{\text{render}}$ 是布局与绘制等渲染操作的耗时,通常目标控制在 8.3ms 以内。
    Trender\displaystyle T_{\text{render}}
  3. 任务开销$T_{\text{task}}$ 是主线程上 JS 推理这类长任务的耗时,它会挤占渲染预算。
    Ttask\displaystyle T_{\text{task}}
  4. 预算判据要维持 60fps,一帧总时间必须不超过 16.7ms。
    Tframe≤16.7ms\displaystyle T_{\text{frame}} \le 16.7\text{ms}

Worker原理

老板请来了帮厨,在后厨另起一个灶台:有自己的火、自己的锅、自己的备料台。Web Worker 就是浏览器给 JavaScript 开的“第二个灶台”,它运行在独立的操作系统线程里,与主线程真正并行。

帮厨再能干也有规矩:他不能端着锅直接跑到大堂,不能亲手把菜递给顾客——Worker 里没有 window 也没有 document,访问不了 DOM。它是消息驱动的:只能通过 postMessage 接收任务、发回结果。这规矩看似限制多,却正好适合计算密集的 AI 推理。尤其当搭配 WASM 或 WebGPU 时,推理代码可以在 Worker 里全速运转,完全不打扰主线程出菜。

不过,任何通信都有成本。主厨和帮厨之间只有那个小窗口,递一次菜既要写单子、又要传盘子、还要确认收讫:

所以“帮厨”不是万能的:通信有开销,任务要足够重才值得“端出去”。但在 AI 推理这种动辄几十毫秒的重活面前,Worker 的收益远大于传菜的成本。好消息是,传菜口虽然小,规矩却很简单。

L=Tserialize+Ttransfer+Tdeserialize+Tprocess\displaystyle L = T_{\text{serialize}} + T_{\text{transfer}} + T_{\text{deserialize}} + T_{\text{process}}
一次消息往返的总延迟 = 序列化时间 + 传输时间 + 反序列化时间 + 真正的处理时间。并行省下的是“处理时间”,但别忘了前后三笔开销。
  1. 单次通信延迟$L$ 表示一条消息从主线程发出、Worker 处理完再返回的往返总延迟。
    L\displaystyle L
  2. 序列化与拷贝$T_{\text{serialize}}$ 是结构化克隆把数据复制一份的时间,数据越大耗时越长。
    Tserialize\displaystyle T_{\text{serialize}}
  3. 传输与还原$T_{\text{transfer}} + T_{\text{deserialize}}$ 是数据在线程间传递、Worker 侧重新还原的时间。
    Ttransfer+Tdeserialize\displaystyle T_{\text{transfer}} + T_{\text{deserialize}}
  4. 推理处理$T_{\text{process}}$ 是 Worker 内真正执行推理计算的时间,这是并行化省下的部分。
    Tprocess\displaystyle T_{\text{process}}

通信机制

传菜口是主厨和帮厨之间唯一的小窗口:把盘子放上去,那边取走;那边把菜放上来,这边端走。Web Worker 的通信就是这样——postMessage 放盘子,onmessage 端盘子。

postMessage 很慷慨,能传对象、数组、ArrayBuffer,甚至嵌套结构。但它默认做的是“结构化克隆”:相当于把菜谱完整复印一份再递过去,主线程和 Worker 各持一册副本,互不影响。这样一来,你改你的,我改我的,永远不会抢同一张菜谱。

好处是绝对安全,代价是复印要花钱。传一份几 KB 的配置,成本可以忽略;但每次传几百 MB 的张量,复印费就变成大问题了。

传菜口传递了消息,也暴露了瓶颈:复印整本菜谱很贵。庆幸的是,餐厅还提供了一种“整锅端”的服务——这就是接下来要讲的 Transferable。

Ccopy=O(n)\displaystyle C_{\text{copy}} = O(n)
结构化克隆的拷贝成本与数据大小 $n$ 成线性关系——数据越大,复印越慢,而且这“复印”本身还占着主线程的 CPU。
  1. 拷贝成本$C_{\text{copy}}$ 表示 postMessage 默认深拷贝数据的开销,以 CPU 时间计。
    Ccopy\displaystyle C_{\text{copy}}
  2. 线性增长当数据规模为 $n$ 时,拷贝耗时随 $n$ 线性增加,即 $O(n)$ 复杂度。
    O(n)\displaystyle O(n)

数据传输

复印菜谱 vs 整锅端过去:如果每次都要复印 100MB 的模型权重,传菜窗口前会排起长队。浏览器为此提供了 Transferable(可转移对象):把 ArrayBuffer 这类大块数据“整锅端”到 Worker,而不是复印。

端锅的意思是所有权转移:数据以接近常数级的代价直接移交,主线程这口锅瞬间变成“空锅”——原对象被 detach,不能再读写。这像极了把老汤锅端给帮厨后,主厨手上只剩一个锅盖。如果之后主线程还想用那口锅,只能等帮厨把锅端回来。

Transferable 特别适合传递模型权重、图像张量、音频缓冲区这类大块二进制数据。

锅端过去之后,汤怎么炖?一整头牛不可能直接扔进锅里。聪明的帮厨会把牛排成小块分批炖——这就是任务分片。

Ctransfer=O(1)≪O(n)\displaystyle C_{\text{transfer}} = O(1) \ll O(n)
转移成本是常数级,远小于深拷贝的线性成本。大块数据务必走“端锅”,别走“复印”。
  1. 转移成本$C_{\text{transfer}}$ 表示用 Transferable 转移数据的开销,近似一次指针移交,复杂度 O(1)。
    Ctransfer=O(1)\displaystyle C_{\text{transfer}} = O(1)
  2. 远小于符号 $\ll$ 表示“远小于”,这里强调转移比深拷贝快一个量级以上。
    O(1)≪O(n)\displaystyle O(1) \ll O(n)

任务分片

一头牛不能整头下锅,要切成牛腩块分批炖。AI 推理也一样:把一大份输入数据切成若干 chunk(块),逐个交给 Worker 推理,边算边回报进度。顾客每等一批就能先吃到一小盘,而不是干等三小时。

分片最大的价值是给 UI“喘气”的机会。主线程可以在块与块之间的空隙渲染帧、处理点击;用户看到的是进度条在走,而不是“已无响应,是否关闭页面”。

但切块不是万灵药:切得太碎,传菜的来回次数暴增;切得太大,单块推理又把主线程憋住。而且并行收益不是线性的——Amdahl 定律告诉我们,串行部分决定了加速比的天花板。

切块炖肉的好处是主厨能抽身出别的小菜,代价是多了合并这道工序。炖好的牛腩块要一盘盘端回桌上,这就轮到结果回传登场了。

S=1(1−P)+PN\displaystyle S = \frac{1}{(1-P) + \frac{P}{N}}
加速比 = 1 除以(串行比例 + 并行比例 ÷ 线程数)。就算开 100 个灶台,只要串行部分占 20%,加速上限也只有 5 倍。
  1. 加速比$S$ 表示使用多线程后总耗时相对串行的提升倍数。
    S\displaystyle S
  2. 并行比例$P$ 是任务中可并行部分占总任务的比例,取值在 0 到 1 之间。
    P\displaystyle P
  3. 线程数量$N$ 是参与并行执行的 Worker 或 CPU 核心数量。
    N\displaystyle N
  4. 整体公式$1-P$ 是串行部分,无法靠加线程消除,它决定了加速比上限。
    S=1(1−P)+PN\displaystyle S = \frac{1}{(1-P)+\frac{P}{N}}

结果回传

炖好一批端出一批,客人先垫垫肚子。Worker 算完一块,就通过 postMessage 把结果传回来,主线程收到后在 onmessage 回调里做增量更新——不必等全部算完再一次性渲染。

结果回传是异步事件驱动的:推理完成 → 消息发回 → 主线程更新对应区域。对于大模型对话,可以做成流式输出:每个 token 生成后立刻回传,让文字“逐字打出来”,用户马上看到回应,而不是盯着空白对话框怀疑程序死了。

回传时还要考虑错误处理:Worker 可能抛异常、消息可能因数据格式问题解析失败,都需要独立的错误通道捕获并触发重试。

边炖边出菜,顾客边吃边等,体验就完全不同了。不过,炖汤之前还有一个更隐形的坑——食材预处理。要是每次都在大堂现洗现切,那客人照样等疯。

Ttotal=∑i=1k(Tinfer,i+Tpost,i)\displaystyle T_{\text{total}} = \sum_{i=1}^{k} \left( T_{\text{infer},i} + T_{\text{post},i} \right)
分片任务的总耗时 = 每一块“推理时间 + 回传合并时间”的累加。流式和增量渲染的意义,是让用户“边吃边等”,而不是把等待堆到最后。
  1. 总耗时$T_{\text{total}}$ 表示分片任务全部完成、结果全部上屏所需的总时间。
    Ttotal\displaystyle T_{\text{total}}
  2. 单块推理$T_{\text{infer},i}$ 是第 $i$ 块数据在 Worker 中推理的耗时。
    Tinfer,i\displaystyle T_{\text{infer},i}
  3. 单块回传$T_{\text{post},i}$ 是第 $i$ 块结果回传、合并并渲染的耗时。
    Tpost,i\displaystyle T_{\text{post},i}
  4. 逐块累加总时间等于所有块“推理 + 回传”之和,用求和符号 $\sum$ 表示。
    ∑i=1k(Tinfer,i+Tpost,i)\displaystyle \sum_{i=1}^{k} \left( T_{\text{infer},i} + T_{\text{post},i} \right)

模型转移

帮厨的备料间:把难处理的半成品提前搬进去,出菜时才不用现洗现切。AI 场景里,模型加载、编译、预热(warmup)就是最重的“备料”——动辄几秒钟,如果放在主线程跑,用户会看到一个漫长的白屏。

解决办法是把加载和预热全部塞进 Worker:Worker 启动时默默把模型文件读进来、编译好、跑一次空推理热身,然后进入“待命”状态。主线程全程无感,第一次真正推理时模型已经“就绪”。

备料的成本不是白花的——它会被后续一次次推理摊薄:

需要注意的是,Worker 里的模型和主线程里的对象是“一人一份”的内存开销,加载大模型时要紧盯内存仪表。备料间准备好了,最后一步就是整个餐厅的运营调度。

Tamortized=TloadM+Tinfer\displaystyle T_{\text{amortized}} = \frac{T_{\text{load}}}{M} + T_{\text{infer}}
单次推理的成本 = 加载成本 ÷ 推理次数 + 单次推理时间。推理次数越多,备料成本越薄,最终逼近纯推理时间。
  1. 摊还成本$T_{\text{amortized}}$ 是模型加载成本在多次推理间平均分摊后,单次推理承担的等效成本。
    Tamortized\displaystyle T_{\text{amortized}}
  2. 加载时间$T_{\text{load}}$ 是模型下载、编译、预热的总耗时,只在 Worker 启动时发生一次。
    Tload\displaystyle T_{\text{load}}
  3. 推理次数$M$ 是该 Worker 上执行的推理请求总次数,$M$ 越大摊得越薄。
    M\displaystyle M
  4. 整体公式摊薄后的单次成本等于 $\frac{T_{\text{load}}}{M}$ 加上纯推理时间 $T_{\text{infer}}$。
    Tamortized=TloadM+Tinfer\displaystyle T_{\text{amortized}} = \frac{T_{\text{load}}}{M} + T_{\text{infer}}

工程落地

餐厅的调度台:不是灶台越多越好,而是传菜、出菜、翻台要平衡。落地 Web Workers 推理时,同样要管理好 Worker 的生命周期与任务队列。

最常用的模式是 Worker 池:启动若干常驻 Worker,任务队列按空闲情况分发推理请求。千万别“每来一个任务就 new 一个 Worker”——创建线程、加载脚本、编译 WASM、预热模型,一套流程跑下来可能几百毫秒甚至几秒,比推理本身还贵。

调度还要权衡吞吐与响应:低延迟的小任务走“快速通道”,大批量任务分批投递;Worker 偶发崩溃要能自动重建、任务重试。整体吞吐模型如下:

好餐厅赢在调度,不靠猛火。主厨出菜、帮厨炖汤、传菜口高效流转、备料间提前就绪——这套“并行餐厅”的运营哲学,就是 Web Workers 与 AI 推理的全部精髓。

R=NTqueue+Tinfer+Ttransfer\displaystyle R = \frac{N}{T_{\text{queue}} + T_{\text{infer}} + T_{\text{transfer}}}
吞吐量 = 任务总数 ÷(排队时间 + 推理时间 + 传输时间)。好的调度是让三项之和最小,而不是无脑堆线程。
  1. 整体吞吐$R$ 表示单位时间内完成的推理请求数量,是衡量系统效率的核心指标。
    R\displaystyle R
  2. 任务总数$N$ 是一段时间内提交的推理请求总数。
    N\displaystyle N
  3. 排队与传输$T_{\text{queue}}$ 是任务在队列中等待的时间,$T_{\text{transfer}}$ 是数据来回传输的时间。
    Tqueue+Ttransfer\displaystyle T_{\text{queue}} + T_{\text{transfer}}
  4. 整体公式总耗时等于排队、推理、传输三部分之和,吞吐量是它的倒数关系。
    R=NTqueue+Tinfer+Ttransfer\displaystyle R = \frac{N}{T_{\text{queue}} + T_{\text{infer}} + T_{\text{transfer}}}

AI 应用场景

实时人脸关键点检测

阶段:前端推理部署 / 浏览器端 ML

解决的问题:传统做法把 MediaPipe FaceMesh 这类 WASM 推理直接跑在主线程,每帧推理耗时 20-40ms,超过 16.7ms 的帧预算,视频画面卡顿、面部追踪滞后。把推理搬进 Worker 后,主线程只负责把摄像头帧“端锅”(Transferable)递给 Worker 并接收关键点结果,UI 保持 60fps 流畅。

具体案例:一款微信小程序里的虚拟试妆工具,用户对着摄像头摇头张嘴,特效实时贴合面部。推理在 Worker 中并行执行,主线程同步渲染 AR 贴纸,即在低端安卓机上也保持不掉帧。

浏览器端大模型对话助手

阶段:推理服务 / 流式生成

解决的问题:在浏览器里用 transformers.js 跑 1B 级别的大模型,逐 token 生成需要数秒。若在主线程生成,用户打字都会卡顿,更看不到“逐字蹦出”的对话感。将模型加载与 token 生成全部移入 Worker,每个 token 通过 postMessage 流式回传,主线程只做增量渲染。

具体案例:一个开源 AI 文档助手,在本地浏览器中对用户上传的 PDF 做摘要问答。模型在 Worker 中常驻,回答时文字逐字输出且页面丝滑,离线也能使用,隐私数据不出本机。

批量图像风格迁移

阶段:推理优化 / 任务分片

解决的问题:用户一次上传 20 张照片做风格化,逐一推理会导致进度不可见、页面假死。将图片切块或按张分片,Worker 池并行处理,每完成一张立刻回传缩略图并更新进度条,传输大图统一用 Transferable 避免深拷贝卡顿。

具体案例:某电商装修工具的风格迁移功能,支持批量处理商品图。分片任务队列配合 4 个常驻 Worker,20 张图的处理进度实时可见,用户可边等边预览已完成的作品。

Python 演示

python30 行
import timefrom concurrent.futures import ThreadPoolExecutor  def heavy_inference(seconds):    """模拟一次大模型推理:占住 CPU 一段时间后返回结果。"""    time.sleep(seconds)    return {"answer": 42}  def main():    print("场景A:推理直接跑在主线程(阻塞)")    t0 = time.time()    result = heavy_inference(1.0)    print(f"推理耗时 {time.time() - t0:.2f}s,期间界面完全卡死")     print("\n场景B:推理放入 Worker 线程(非阻塞)")    executor = ThreadPoolExecutor(max_workers=2)    futures = [executor.submit(heavy_inference, 1.0) for _ in range(2)]    print("已提交两个推理任务,主流程立刻返回,无需等待")    time.sleep(0.1)    print("主流程继续响应:渲染动画帧、处理点击事件、滚动画布")    for fut in futures:        r = fut.result()        print(f"收到 Worker 回传结果:答案 = {r['answer']}")    executor.shutdown()  if __name__ == "__main__":    main()
目的:用 Python 的线程池模拟“主线程阻塞 vs Worker 非阻塞”两种模式,直观看到任务提交后主流程是否立即返回。
  1. executor = ThreadPoolExecutor(max_workers=2)创建 2 个“帮厨”线程,对应前端 Worker 池。
  2. futures = [executor.submit(heavy_inference, 1.0) for _ in range(2)]提交推理任务后立即返回,等价于 postMessage 发消息不等待。
  3. print("主流程继续响应:...")证明主流程没有被推理任务占住,对应 UI 持续流畅。
  4. fut.result()阻塞等待单个任务结果,等价于在主线程 onmessage 回调里接收 Worker 回传。
  5. executor.shutdown()关闭线程池,对应页面销毁时 terminate Worker。

常见误区

误区“我开了 8 个 Web Worker,推理速度应该提升到原来的 8 倍吧?”

正解推理任务大多受单个计算核心的 WASM 执行限制,多 Worker 只是“多个灶台各炖各的”,同一模型权重还会被复制多份吃掉内存。线程切换和调度开销更会在 Worker 增多后反噬性能。Amdahl 定律早就说了:串行部分决定加速比上限。更实际的做法是按设备核数创建 2-4 个常驻 Worker。

为何常见:人们容易把“多线程”直觉等同于“线性加速”,忽略了串行部分和线程调度开销。

误区“反正消息是异步的,几兆的数组发过去应该没多大影响。”

正解异步不等于零成本。postMessage 默认结构化克隆是深拷贝,拷贝 100MB 的 ArrayBuffer 本身就要占主线程几十毫秒 CPU,照样卡。大块二进制数据应使用 Transferable 转移所有权,近乎零拷贝;但转移后原数据就不可用了。

为何常见:肉眼看不到“等待”,就误以为没有开销,忽略了拷贝动作本身消耗的 CPU。

误区“Worker 也是 JS 环境,应该能 document.getElementById 改页面吧?”

正解Worker 没有 window 和 document,访问不了 DOM,无法操作页面。它只能通过 postMessage 与主线程通信,由主线程代为更新 UI。这也是为什么推理结果必须回传、渲染始终留在主线程。

为何常见:把“JS 环境”理解得太宽泛,混淆了“能跑 JS”与“能碰页面”两个概念。

误区“反正推理已经在 Worker 了,页面再卡就是浏览器的问题。”

正解主线程仍要承担数据序列化、上传纹理到 GPU、合并渲染结果、防抖节流等开销;如果每块结果都触发全量重绘,照样卡。需要结合任务分片、增量渲染、批量合并等手段综合治理。

为何常见:把“阻塞”简单归因于单一计算任务,忽略了整条渲染管线里其他主线程开销。

误区“用完就关,下次再开,状态不会串,最干净。”

正解创建 Worker 要初始化线程、加载脚本、编译 WASM、加载模型,总开销可达几百毫秒甚至数秒,比推理本身还贵。正确做法是 Worker 池化:启动几个常驻 Worker,用任务队列分发请求,异常时再重建。

为何常见:用“用完即弃”的对象思维套在线程上,忽略了线程初始化和模型加载的高昂成本。

面试题

Q1. Web Worker 中的代码实际运行在哪种环境?

单选题 · easy

  • A.主线程的隔离沙箱中
  • B.浏览器的独立线程中← 正确答案
  • C.GPU 的计算核心之上
  • D.服务端的 Node 进程内

答案:B。Web Worker 运行在浏览器分配的独立操作系统线程里,与主线程并行。它不在主线程沙箱里,也不依赖 GPU 或 Node 进程,因此选 B。

Q2. postMessage 默认情况下如何传递数据?

单选题 · easy

  • A.按引用地址直接共享
  • B.结构化克隆完成拷贝← 正确答案
  • C.统一序列化成 JSON 串
  • D.移动数据并清空原值

答案:B。postMessage 默认使用结构化克隆(structured clone),相当于“复印菜谱”,两端各持一个副本。按引用共享、序列化成 JSON、移动原值都不准确,故选 B。

Q3. ArrayBuffer 通过 Transferable 转移给 Worker 后,主线程中的该对象会怎样?

单选题 · mid

  • A.仍可正常读写数据
  • B.变成只读数据对象
  • C.变为脱离状态不可再用← 正确答案
  • D.自动复制出一份备份

答案:C。Transferable 是“整锅端”式的所有权转移,主线程中的原 ArrayBuffer 会被 detach,进入不可用状态,就像端走汤锅后主厨手上只剩锅盖。故选 C。

Q4. 浏览器页面出现明显卡顿,最直接的原因是什么?

单选题 · easy

  • A.请求的图片体积过大
  • B.事件循环被长任务占满← 正确答案
  • C.浏览器缓存没有命中
  • D.使用的字体数量太多

答案:B。卡顿的本质是事件循环被长任务(如主线程上的 AI 推理)占满,渲染帧得不到执行机会。图片、缓存、字体是间接因素,故选 B。

Q5. 下列哪些做法能减轻推理对主线程的阻塞?

多选题 · mid

  • A.用 Worker 并行执行推理← 正确答案
  • B.把推理切块分批执行← 正确答案
  • C.用 Transferable 传大数组← 正确答案
  • D.推理结果同步阻塞返回

答案:ABC。Worker 把推理移出主线程,任务分片给 UI 留出渲染空隙,Transferable 降低传输时的拷贝开销——三者都能减轻阻塞。同步阻塞返回恰恰会加剧卡顿,故选 ABC。

Q6. 关于 Web Worker 的特性,下列说法正确的是?

多选题 · mid

  • A.Worker 内可以操作 DOM 节点
  • B.Worker 通过 postMessage 通信← 正确答案
  • C.Worker 与主线程并行运行← 正确答案
  • D.Worker 能直接修改页面样式

答案:BC。Worker 是独立线程,与主线程并行,两者只能通过 postMessage 通信。Worker 没有 window/document,不能操作 DOM 或修改样式,故 A、D 错误,选 BC。

Q7. Amdahl 定律指出,增加线程数带来的加速比上限取决于什么?

单选题 · hard

  • A.并行部分占用总时长的比例
  • B.串行部分占用总时长的比例← 正确答案
  • C.线程数量与内存带宽的乘积
  • D.模型参数量与显存的大小

答案:B。Amdahl 定律中,串行部分 $1-P$ 无法靠加线程消除,它决定加速比天花板。并行比例再高,只要串行部分存在,加速就有上限,故选 B。

Q8. 任务分片推理时,下列哪项不属于必要环节?

单选题 · hard

  • A.把输入数据切成多个小块
  • B.逐块推理并回报进度
  • C.将各块结果按序合并
  • D.保证各块结果完全相同← 正确答案

答案:D。分块推理的三个必要环节是切分、逐块推理回报进度、合并结果。每块处理不同数据,结果本来就不同,“保证各块完全相同”没有意义,故选 D。

Q9. 把 AI 模型放到 Worker 中加载的主要收益是什么?

单选题 · mid

  • A.推理精度得到大幅提升
  • B.加载与预热不阻塞主线程← 正确答案
  • C.模型文件体积自动减小
  • D.推理完全不再需要内存

答案:B。模型加载与预热耗时长,移入 Worker 后主线程不感知这些开销,可继续渲染界面。精度、体积、内存需求都不会因放 Worker 而改变,故选 B。

Q10. 工程化落地 Worker 推理时,推荐的做法包括哪些?

多选题 · extreme

  • A.创建 Worker 池并复用实例← 正确答案
  • B.用任务队列管理推理请求← 正确答案
  • C.监听异常并及时重建← 正确答案
  • D.每来一个任务新建 Worker

答案:ABC。Worker 池复用避免重复创建的开销,任务队列让调度有序,异常监听与重建保证可用性——这是工程化三件套。每次 new Worker 开销巨大且易导致内存膨胀,故选 ABC。