先记住这个答案
同线程切片把长计算分成有界片段并让出事件循环,能改善响应性但不会获得多核并行。worker_threads 适合可独立执行的 JavaScript CPU 工作,需要考虑消息复制或转移以及线程池容量。独立进程提供不同的故障与资源边界,启动和通信成本也更高;长任务还可交给外部作业系统。选择前应先优化算法和输入规模,再比较计算量、数据传输、隔离需求和可接受延迟,并限制并发与队列,避免把主线程阻塞换成无界积压。
- 切片改善让出机会,不等于并行计算
- 工作线程与进程都有传输和管理成本
- 卸载后仍需限制队列、并发和任务生命周期
先确认慢的是 CPU,而不是外部等待
解析大型数据、复杂搜索或图像处理可能真正消耗 CPU,慢数据库请求则可能主要在等待。应通过调用栈和请求分段确认,避免为了外部等待创建大量线程,增加成本却没有消除瓶颈。
算法复杂度、输入上限和重复计算往往比调度位置更值得先处理。一个随输入急剧增长的算法搬到工作线程后仍会很慢,只是换了占用位置;应先明确最坏输入和业务允许的计算预算。
选择切片时接受它仍在同一线程执行
如果任务能拆成许多轻量片段,可以在片段之间使用 setImmediate 等方式交回控制,减少单次占用时间。它通常增加一些调度开销,重点是让其他请求有机会运行,而不是承诺总计算更快。
单个步骤本身就很重时,按固定条数切片仍可能产生长阻塞。需要更细分算法或按时间预算调整,并在每片边界检查取消条件;把一百万项分成十片不代表每片天然足够短。
并行执行需要完整的任务协议
工作线程有独立执行环境,传递数据可能产生复制成本,转移 ArrayBuffer 则会改变发送方对原缓冲的使用权。应按输入规模和输出需求选择传输方式,共享数据还需要同步设计。
线程池或进程池应使用任务标识关联结果,限制同时运行和等待数量,并处理崩溃、超时及迟到结果。带外部副作用的任务还需要幂等和补偿,不能认为重启一个执行器就自动回滚之前的工作。
容易答错的地方
- 给计算函数加 async 就认为已经卸载
- async 改变返回值与续体表达,并不把同步计算移到其他线程。调用栈中的重计算仍会占用当前执行资源,应实际测量并选择切片或独立执行机制,而不是只改变函数签名。
- 每个请求都创建一个 Worker 或进程
- 启动、内存和调度成本会随并发放大,可能导致资源竞争和更长尾延迟。应评估复用池、排队上限与过载策略,并按机器资源和任务规模控制容量。
面试官还会怎么问?
worker_threads 更适合 CPU 还是普通异步 I/O?
其主要价值在可并行的 JavaScript CPU 工作,普通异步 I/O 已有相应非阻塞机制。是否放到线程还要看所用库和隔离需求,不能把所有异步请求都迁移到 Worker 作为通用加速方案。
传递大型对象为什么可能抵消收益?
序列化、复制和反序列化本身消耗时间与内存,计算较小时这些成本可能超过节省的主线程工作。应测量完整往返,并考虑减少传输字段或使用合适的缓冲转移协议。
取消任务是不是直接 terminate 就够了?
终止执行器可以中断其运行,但任务状态、共享数据和已发生的外部副作用仍要处理。优先设计可协作取消与明确收尾,必要时强制终止,并确认配额和资源不会因迟到消息重复释放。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。