>  기사  >  웹 프론트엔드  >  Nodejs의 다중 스레드 작업에 대한 간략한 토론

Nodejs의 다중 스레드 작업에 대한 간략한 토론

青灯夜游
青灯夜游앞으로
2021-06-23 10:31:354496검색

nodejs는 단일 스레드이지만 여전히 다중 스레드 작업을 허용합니다. 이 기사는 노드 스레드로 시작하여 Nodejs의 다중 스레드 작업에 대해 설명하고 Worker_threads 템플릿을 소개합니다.

Nodejs의 다중 스레드 작업에 대한 간략한 토론

이 기사의 테스트 환경:
시스템: macOS Mojave 10.14.2
CPU: 4 코어 2.3 GHz
노드: 10.15.1

[권장 학습: "nodejs tutorial"]

Node 스레드 이야기에서

대부분의 사람들은 Node가 싱글 스레드인 것으로 알고 있으므로 Node가 시작된 후 스레드 수는 1이 되어야 합니다. 실험을 통해 확인해 보겠습니다. [추천 학습: "nodejs tutorial"]

setInterval(() => {
  console.log(new Date().getTime())
}, 3000)

Nodejs의 다중 스레드 작업에 대한 간략한 토론

Node 프로세스가 7개의 스레드를 차지하는 것을 볼 수 있습니다. 스레드가 왜 7개인가요?

우리 모두는 Node의 핵심이 v8 엔진이라는 것을 알고 있습니다. Node가 시작되면 v8 인스턴스가 멀티 스레드로 생성됩니다.

  • 메인 스레드: 코드를 컴파일하고 실행합니다.
  • 컴파일/최적화 스레드: 메인 스레드가 실행되면 코드가 최적화될 수 있습니다.
  • Analyzer 스레드: 코드 실행 시간을 기록하고 분석하여 Crankshaft가 코드 실행을 최적화할 수 있는 기반을 제공합니다.
  • 가비지 수집을 위한 여러 스레드.

그래서 사람들이 Node를 싱글 스레드라고 자주 말한다면 JavaScript의 실행은 싱글 스레드이지만 Javascript의 호스트 환경은 Node든 브라우저든 멀티 스레드라는 뜻입니다.

Node에는 두 가지 컴파일러가 있습니다.
full-codegen: js를 간단하지만 느린 기계적 코드로 간단하고 빠르게 컴파일합니다.
크랭크샤프트: 고성능 실행 코드를 컴파일하는 비교적 복잡한 실시간 최적화 컴파일러입니다.

일부 비동기 IO는 추가 스레드를 차지합니다

여전히 위의 예에서는 타이머가 실행되는 동안 파일을 읽습니다.

const fs = require('fs')

setInterval(() => {
    console.log(new Date().getTime())
}, 3000)

fs.readFile('./index.html', () => {})

Nodejs의 다중 스레드 작업에 대한 간략한 토론

스레드 수가 11개가 됩니다. 이는 일부 IO가 있기 때문입니다. Node에서 작업(DNS, FS) 및 일부 CPU 집약적인 계산(Zlib, Crypto)을 수행하면 Node의 스레드 풀이 활성화되고 스레드 수가 11개가 되므로 스레드 풀의 기본 크기는 4입니다.

스레드 풀 기본 크기를 수동으로 변경할 수 있습니다.

process.env.UV_THREADPOOL_SIZE = 64

코드 한 줄로 스레드를 71로 쉽게 변경할 수 있습니다.

Nodejs의 다중 스레드 작업에 대한 간략한 토론

클러스터는 멀티스레드인가요?

Node의 단일 스레드는 CPU 활용도 부족, 포착되지 않은 예외로 인해 전체 프로그램이 종료되는 등 몇 가지 문제도 발생합니다. 클러스터 모듈은 Node에서 제공되기 때문에 클러스터는 child_process의 캡슐화를 구현하고, fork 방식을 통해 자식 프로세스를 생성하여 다중 프로세스 모델을 구현합니다. 예를 들어 우리가 가장 자주 사용하는 pm2가 그 중 가장 대표적인 예이다.

클러스터 데모를 살펴보겠습니다.

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on(&#39;exit&#39;, (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工作进程可以共享任何 TCP 连接。
  // 在本例子中,共享的是 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(&#39;Hello World&#39;);
  }).listen(8000);
  console.log(`工作进程 ${process.pid} 已启动`);
}

이때 활동 모니터를 살펴보세요.

Nodejs의 다중 스레드 작업에 대한 간략한 토론

총 9개의 프로세스가 있으며 그 중 하나가 메인 프로세스입니다. CPU 수 x CPU 수 코어 = 2 x 4 = 8개의 하위 프로세스 프로세스.

그러므로 child_process도 클러스터도 멀티 스레드 모델이 아니라 멀티 프로세스 모델입니다. 개발자들은 싱글 쓰레드 모델의 문제점을 인지하고 있지만 근본적으로 문제를 해결하지 못하고 멀티 쓰레드를 시뮬레이션하기 위한 멀티 프로세스 방식을 제공하고 있습니다. 이전 실험에서 Node(V8) 자체에는 멀티스레딩 기능이 있지만 개발자는 이 기능을 제대로 활용하지 못하고 대신 Node의 하위 계층에서 제공하는 방식으로 멀티스레딩을 사용한다는 것을 알 수 있습니다. Node 관계자는 다음과 같이 말했습니다:

C++ 애드온을 개발하여 내장된 Node Worker Pool을 사용할 수 있습니다. 이전 버전의 Node에서는 NAN을 사용하여 C++ 애드온을 빌드하고, 최신 버전에서는 node-webworker-threads를 사용하세요. Node의 작업자 풀에 액세스할 수 있는 JavaScript 전용 방법을 제공합니다.

그러나 JavaScript 개발자에게는 Node의 멀티스레딩 기능을 사용하는 표준적이고 사용하기 쉬운 방법이 없었습니다.

True - 노드 멀티스레딩

Node 10.5.0이 출시될 때까지 공식은 Node에 실제 멀티스레딩 기능을 제공하기 위해 실험적인 모듈 worker_threads을 제공했습니다.

먼저 간단한 데모를 살펴보겠습니다.

const {
  isMainThread,
  parentPort,
  workerData,
  threadId,
  MessageChannel,
  MessagePort,
  Worker
} = require(&#39;worker_threads&#39;);

function mainThread() {
  for (let i = 0; i < 5; i++) {
    const worker = new Worker(__filename, { workerData: i });
    worker.on(&#39;exit&#39;, code => { console.log(`main: worker stopped with exit code ${code}`); });
    worker.on(&#39;message&#39;, msg => {
      console.log(`main: receive ${msg}`);
      worker.postMessage(msg + 1);
    });
  }
}

function workerThread() {
  console.log(`worker: workerDate ${workerData}`);
  parentPort.on(&#39;message&#39;, msg => {
    console.log(`worker: receive ${msg}`);
  }),
  parentPort.postMessage(workerData);
}

if (isMainThread) {
  mainThread();
} else {
  workerThread();
}

위 코드는 메인 스레드에서 5개의 하위 스레드를 열고, 메인 스레드는 하위 스레드에 간단한 메시지를 보냅니다.

由于 worker_thread 目前仍然处于实验阶段,所以启动时需要增加 --experimental-worker flag,运行后观察活动监视器:

Nodejs의 다중 스레드 작업에 대한 간략한 토론

不多不少,正好多了五个子线程。

worker_thread 模块

worker_thread 核心代码

worker_thread 模块中有 4 个对象和 2 个类。

  • isMainThread: 是否是主线程,源码中是通过 threadId === 0 进行判断的。
  • MessagePort: 用于线程之间的通信,继承自 EventEmitter。
  • MessageChannel: 用于创建异步、双向通信的通道实例。
  • threadId: 线程 ID。
  • Worker: 用于在主线程中创建子线程。第一个参数为 filename,表示子线程执行的入口。
  • parentPort: 在 worker 线程里是表示父进程的 MessagePort 类型的对象,在主线程里为 null
  • workerData: 用于在主进程中向子进程传递数据(data 副本)

来看一个进程通信的例子:

const assert = require(&#39;assert&#39;);
const {
  Worker,
  MessageChannel,
  MessagePort,
  isMainThread,
  parentPort
} = require(&#39;worker_threads&#39;);
if (isMainThread) {
  const worker = new Worker(__filename);
  const subChannel = new MessageChannel();
  worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
  subChannel.port2.on(&#39;message&#39;, (value) => {
    console.log(&#39;received:&#39;, value);
  });
} else {
  parentPort.once(&#39;message&#39;, (value) => {
    assert(value.hereIsYourPort instanceof MessagePort);
    value.hereIsYourPort.postMessage(&#39;the worker is sending this&#39;);
    value.hereIsYourPort.close();
  });
}

更多详细用法可以查看官方文档

多进程 vs 多线程

根据大学课本上的说法:“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试就够了,但是在实际工作中,我们还是要根据需求合理选择。

下面对比一下多线程与多进程:

属性 多进程 多线程 比较
数据 数据共享复杂,需要用IPC;数据是分开的,同步简单 因为共享进程数据,数据共享简单,同步复杂 各有千秋
CPU、内存 占用内存多,切换复杂,CPU利用率低 占用内存少,切换简单,CPU利用率高 多线程更好
销毁、切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度很快 多线程更好
coding 编码简单、调试方便 编码、调试复杂 多进程更好
可靠性 进程独立运行,不会相互影响 线程同呼吸共命运 多进程更好
分布式 可用于多机多核分布式,易于扩展 只能用于多核分布式 多进程更好

上述比较仅表示一般情况,并不绝对。

work_thread 让 Node 有了真正的多线程能力,算是不小的进步。

更多编程相关知识,请访问:编程视频!!

위 내용은 Nodejs의 다중 스레드 작업에 대한 간략한 토론의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제