菜单

工作线程架构

相关源文件

本文档详细介绍了 Node.js Worker Threads 架构,解释了 Node.js 如何通过隔离的线程实现并行 JavaScript 执行。它涵盖了 worker 线程的创建方式、它们之间的通信方式以及它们的生命周期管理。有关用于创建和与 worker 线程交互的模块 API 的信息,请参阅 worker_threads 模块的文档。

Worker 线程概述

Worker 线程提供了一种通过在单独的线程中运行代码来并行执行 JavaScript 的方法。与创建单独进程的 cluster 模块不同,worker 线程共享同一个进程,但拥有自己隔离的 JavaScript 执行环境。

Node.js 中的 Worker 线程的设计遵循以下原则:

  • 隔离性:每个 worker 都在其自己的 V8 隔离环境中运行,拥有独立的 JavaScript 堆。
  • 通信:线程之间通过显式消息传递交换数据。
  • 资源管理:可以为 worker 配置特定的内存限制。
  • 集成性:Worker 共享同一进程,但拥有独立的事件循环。

Worker 线程的主要优势

功能描述
内存共享通过 SharedArrayBuffer 高效共享内存。
较低的开销比创建单独进程的成本更低。
并行执行利用多个 CPU 核心执行 JavaScript。
独立的崩溃处理Worker 中的错误不会导致主应用程序崩溃。

来源:src/node_worker.h28-137 src/node_worker.cc1-50

架构概述

Worker 线程架构由几个关键组件组成,这些组件相互作用,在同一进程中创建和管理独立的 JavaScript 执行环境。

来源:src/node_worker.h28-137 src/node_worker.cc50-146 src/node.cc358-405

关键组件

Worker 类

The Worker 类是 worker 线程系统的核心组件。它充当主线程和子线程之间的桥梁。

主线程中的每个 Worker 实例负责管理:

  • Worker 线程的生命周期。
  • 主线程和 worker 之间的通信通道。
  • Worker 的资源限制。
  • 错误处理和终止。

来源:src/node_worker.h28-137 src/node_worker.cc50-104

WorkerThreadData

The WorkerThreadData 类管理仅在 worker 线程运行时与该线程本身相关的数据。它负责:

  • 创建和管理 worker 的事件循环(uv_loop_t)。
  • 创建和配置 worker 的 V8 隔离环境。
  • 处理 worker 终止时的清理工作。

来源:src/node_worker.cc144-271

Worker 线程创建过程

创建新 worker 时,会经历一个复杂的初始化过程,以创建一个隔离的 JavaScript 环境。

该过程包括以下步骤:

  1. Worker 构造:

    • 主线程创建 Worker 实例。
    • 设置用于通信的消息端口。
    • 配置资源限制和选项。
  2. 线程初始化:

    • 创建新的原生线程。
    • Worker 线程创建自己的事件循环。
    • 初始化新的 V8 隔离环境。
  3. 环境设置:

    • 为 worker 创建 IsolateData 实例。
    • 为 worker 创建新的 Environment
    • 设置全局对象和消息端口。
  4. 代码执行:

    • 加载并执行 worker 的 JavaScript 代码。
    • 启动 worker 的事件循环。

来源:src/node_worker.cc144-339 src/node_worker.cc498-624

V8 隔离环境创建

每个 worker 线程都有自己的 V8 隔离环境,它提供了一个独立的 JavaScript 执行环境。这是 worker 架构的关键部分。

Worker 隔离环境创建的重要方面:

  1. 资源限制:Worker 可以为以下方面设置自定义内存限制:

    • 年轻代堆大小
    • 老年代堆大小
    • 代码区大小
  2. 隔离环境配置:

    • 每个隔离环境都会在平台中注册。
    • 配置堆栈限制。
    • 可以设置堆大小限制。
  3. Worker 上下文关联:

    • 使用 set_worker_context() 将 worker 与其隔离环境数据相关联。

来源:src/node_worker.cc144-227 src/api/environment.cc303-345

线程通信

Worker 通过消息端口与主线程和其他 worker 进行通信。通信模型使用显式的消息传递,而不是共享内存(尽管 SharedArrayBuffer 可用于共享内存)。

创建 worker 时:

  1. 主线程创建一个消息端口对。
  2. 一个端口保留在主线程中。
  3. 另一个端口的数据传递给 worker。
  4. Worker 创建自己的消息端口,连接到主线程的数据。

这会在主线程和 worker 线程之间建立一个双向通信通道。消息端口允许结构化克隆 JavaScript 对象或在线程之间传递某些对象。

来源:src/node_worker.cc73-83 src/node_messaging.cc

资源管理

Worker 线程可以配置各种资源限制,以控制内存使用量并防止单个 worker 消耗过多资源。

内存限制

Worker 支持多种类型的内存限制:

这些限制在 worker 创建期间应用,可以根据 worker 的具体需求进行自定义,以优化资源使用。

引用计数

Worker 实现了类似于其他 Node.js handle 的引用计数机制,允许控制 worker 是否保持事件循环的活动状态。

在底层,这由 Worker 类中的 has_ref_ 属性管理,并传递给 libuv。

来源:src/node_worker.h70-81 src/node_worker.cc104-143

Worker 生命周期

Worker 线程的生命周期包括创建、执行和终止阶段。

Worker 终止

Worker 可以通过几种方式终止:

  1. 自然退出:当 worker 的事件循环没有更多待处理工作时。
  2. 显式终止:通过 worker.terminate() 方法。
  3. 错误终止:当 worker 中发生未捕获的异常时。
  4. 退出方法:通过在 worker 中调用 process.exit()

Worker 终止时,会进行清理过程:

  1. Worker 的环境被标记为停止。
  2. Worker 的事件循环被停止。
  3. Worker 线程被加入(join)。
  4. Worker 的隔离环境被销毁。
  5. 与 worker 相关的资源被释放。

来源:src/node_worker.cc625-734 src/node_worker.cc786-829

线程安全注意事项

Node.js Worker 线程实现了各种机制来确保线程安全:

机制描述
互斥锁保护对共享数据结构的访问。
消息传递显式通信,而非共享状态。
隔离的堆每个线程都有自己的 JavaScript 堆。
线程本地存储环境和隔离环境数据是线程本地的。

The Worker 类本身使用互斥锁来保护可能从主线程和 worker 线程访问的字段。

来源:src/node_worker.h97-99 src/node_worker.cc106-208

调试与检查

Worker 线程通过 inspector 协议支持调试。创建 worker 时,可以为其创建一个 InspectorParentHandle

这允许调试工具连接并调试单个 worker 线程。

来源: src/node_worker.cc92-96 src/api/environment.cc389-465

总结

Node.js Worker Threads 架构提供了一种在同一进程内创建隔离的 V8 环境来并行执行 JavaScript 代码的方式。该设计平衡了隔离的安全性与实用的高效通信机制。

关键架构方面包括:

  • 为每个线程提供独立的 V8 实例
  • 线程间的基于消息的通信
  • 用于控制内存使用的资源限制
  • Worker 的完整生命周期管理

这种架构使 Node.js 应用程序能够有效地利用多核系统,同时保持了 JavaScript 开发人员熟悉的每个 Worker 内部的单线程执行模型。