Worker 多线程编程
简介
在 DejaOS 中, UI 渲染和大部分业务逻辑都可以运行在同一个主线程中。如果在这个主线程上执行任何耗时的操作,例如复杂的数据处理、数据库读写或网络请求,都会导致 UI 渲染被阻塞,从而出现界面卡顿、无响应等问题,严重影响用户体验。
为了解决这个问题,DejaOS 充分利用了 QuickJS 引擎提供的 Worker 能力。Worker 是 QuickJS 中的一个重要概念,它允许开发者创建在后台运行的独立线程,将耗时任务从主线程中剥离出去。这使得主线程可以专注于处理用户交互和 UI 刷新,保持应用的流畅和响应迅速。
可以说,掌握 Worker 是开发高性能 DejaOS 应用的关键。
值得一提的是,DejaOS 的许多核心组件(例如人脸识别 dxFacial、扫码 dxBarcode 等)都依赖于高频轮询机制来获取实时动态数据(如 dxFacial.getDetectionData())。这意味着,任何在主线程或 Worker 线程中的同步耗时操作,都会直接阻塞和延迟这些轮询调用,导致组件功能异常(例如人脸框不更新)。因此,将所有可能耗时的逻辑都封装到独立的 Worker 中,是确保这些组件正常、流畅运行的前提。
无论是主线程还是 Worker,都运行在一个单线程的事件循环模型上。严禁在任何线程中使用 while(true) 或 for(;;) 等同步死循环来等待或处理任务。
这样的代码会永久阻塞当前线程的事件循环,使其无法响应任何新的事件(如用户输入、定时器、网络消息等)。对于主线程,这意味着 UI 卡死;对于 Worker,这意味着它无法再接收任何新任务。
所有耗时或等待操作都必须通过异步、事件驱动的模式来完成,例如使用 dxStd.setInterval、dxStd.setTimeout 或事件回调。
核心概念
1. 独立运行环境
Worker 类似于一个线程,但更准确地说,它像一个独立的“进程”。每个通过 new Worker() 创建的 Worker 实例都是一个完全隔离的 JavaScript 运行环境(VM)。
这意味着:
- 独立的全局作用域: 每个
Worker都有自己的global对象、变量和函数,与主线程和其他Worker完全隔离。 - 无共享内存: 不能直接访问或修改主线程或其他
Worker中的变量或函数。这种设计避免了多线程编程中常见的竞态条件和死锁问题,但也对线程间的数据交换提出了要求。
2. 创建与生命周期
- 创建:
Worker只能由主线程创建。// main.js
const myWorker = new Worker("path/to/worker_script.js"); - 模块路径: 构造函数中的
module_filename是一个字符串,指定了将在新线程中执行的 JS 模块文件路径。这个路径是相对于当前脚本的路径。 - 限制: DejaOS 目前不支持在
Worker内部再创建新的Worker(即嵌套 Worker)。 - 销毁: 当一个
Worker的事件循环中没有任何待处理的任务时(例如,没有设置onmessage监听器、没有运行中的setInterval/setTimeout),并且主线程中已没有任何引用指向该Worker实例时,它才会被垃圾回收机制自动终止并释放资源。
Worker 间通信 vs. 数据共享
Worker 间的交互主要分为两类:通信(Communication) 和 数据共享(Data Sharing)。
-
通信:指一个线程需要通知另一个线程“某件事发生了”。这种场景下,我们推荐使用
dxEventBus,它更侧重于事件的传递。详细用法请参考 dxEventBus 文档。 -
数据共享:指多个线程需要访问或修改同一份数据。直接使用
dxEventBus传递数据会因反复克隆而产生性能开销。更重要的是,它不符合“共享状态”的意图。
为了高效、安全地实现数据共享,DejaOS 借助底层的 C 宿主环境,提供了 dxMap 和 dxQueue 等原生模块。这些模块允许多个线程访问同一块共享内存,实现了零拷贝。详细用法请参考 dxMap & dxQueue 文档。
总结与最佳实践
- 分离耗时任务: 将所有耗时操作,特别是 I/O 密集型任务(如 文件读写、SQLite 数据库操作、MQTT 通信、串口数据收发)和 CPU 密集型任务(如 人脸识别算法)放入
Worker中执行,以保证主线程 UI 的流畅。 - 优先使用
dxEventBus: 使用dxEventBus进行Worker间通信,以构建清晰、解耦的事件驱动应用。 - 使用
dxMap进行数据共享: 当需要在线程间共享状态或高性能地交换数据时,使用dxMap避免数据克隆的开销。 - 保持
Worker职责单一: 建议让每个Worker负责一类特定的后台任务,例如一个Worker专门负责人脸识别算法,另一个专门负责网络通信。 - 控制 Worker 数量: 在 DejaOS 设备上,由于硬件资源有限,建议合理控制
Worker的数量,避免过多占用系统资源。
文档系列导航
本系列文档涵盖了 DejaOS Worker 多线程编程的几个核心模块,帮助您构建高性能应用:
-
学习如何使用
dxEventBus在主线程和 Worker 之间,以及 Worker 与 Worker 之间进行解耦的、事件驱动的通信。 -
掌握如何使用
dxMap和dxQueue实现线程间的零拷贝、高性能数据共享。 -
了解如何基于
dxEventBus实现优雅的跨线程远程过程调用 (RPC),像调用本地函数一样调用其他线程中的函数。 -
学习如何使用
dxWorkerPool线程池来管理和复用 Worker,高效处理大量并发任务。