跳到主要内容

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.setIntervaldxStd.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 宿主环境,提供了 dxMapdxQueue 等原生模块。这些模块允许多个线程访问同一块共享内存,实现了零拷贝。详细用法请参考 dxMap & dxQueue 文档。


总结与最佳实践

  • 分离耗时任务: 将所有耗时操作,特别是 I/O 密集型任务(如 文件读写SQLite 数据库操作MQTT 通信串口数据收发)和 CPU 密集型任务(如 人脸识别算法)放入 Worker 中执行,以保证主线程 UI 的流畅。
  • 优先使用 dxEventBus: 使用 dxEventBus 进行 Worker 间通信,以构建清晰、解耦的事件驱动应用。
  • 使用 dxMap 进行数据共享: 当需要在线程间共享状态或高性能地交换数据时,使用 dxMap 避免数据克隆的开销。
  • 保持 Worker 职责单一: 建议让每个 Worker 负责一类特定的后台任务,例如一个 Worker 专门负责人脸识别算法,另一个专门负责网络通信。
  • 控制 Worker 数量: 在 DejaOS 设备上,由于硬件资源有限,建议合理控制 Worker 的数量,避免过多占用系统资源。

文档系列导航

本系列文档涵盖了 DejaOS Worker 多线程编程的几个核心模块,帮助您构建高性能应用:

  • ./eventbus.md

    学习如何使用 dxEventBus 在主线程和 Worker 之间,以及 Worker 与 Worker 之间进行解耦的、事件驱动的通信

  • ./mapqueue.md

    掌握如何使用 dxMapdxQueue 实现线程间的零拷贝、高性能数据共享

  • ./rpc.md

    了解如何基于 dxEventBus 实现优雅的跨线程远程过程调用 (RPC),像调用本地函数一样调用其他线程中的函数。

  • ./pool.md

    学习如何使用 dxWorkerPool 线程池来管理和复用 Worker,高效处理大量并发任务。