【추천 학습: "nodejs Tutorial"】
Worker를 이해하려면 Node의 최하위 레이어를 이해하는 것이 필요합니다.
Node.js 애플리케이션이 시작되면 다음 모듈이 시작됩니다.
A 프로세스: 프로세스 객체는 Node.js 프로그램 어디에서나 액세스할 수 있고 현재 프로세스에 대한 정보를 제공하는 전역 변수입니다.
하나의 스레드: 단일 스레드는 현재 프로세스에서 동시에 하나의 명령만 실행된다는 의미입니다.
이벤트 루프: 이는 이해해야 할 Node.js의 중요한 부분입니다. JavaScript는 단일 스레드이지만 콜백, 약속, 비동기/대기 등과 같은 구문을 사용하여 운영의 작업을 수행합니다. 시스템은 이벤트 루프를 기반으로 비동기화되므로 노드는 비동기식 비차단 IO의 특성을 갖게 됩니다.
JS 엔진 인스턴스: JavaScript 코드를 실행할 수 있는 프로그램입니다.
Node.js 인스턴스: Node.js 환경을 실행할 수 있는 프로그램입니다.
즉, Node는 단일 스레드에서 실행되며 이벤트 루프에서는 한 프로세스의 작업만 동시에 실행되고, 매번 동시에 하나의 코드만 실행됩니다(여러 조각 코드는 동시에 실행되지 않습니다). 이는 JavaScript를 사용할 때 동시 프로그래밍에 대해 걱정할 필요가 없을 정도로 메커니즘이 간단하기 때문에 매우 효과적입니다.
그 이유는 JavaScript가 원래 클라이언트 측 상호 작용(예: 웹 페이지 상호 작용 또는 양식 유효성 검사)에 사용되었으며 이러한 논리를 처리하는 데 멀티 스레딩과 같은 메커니즘이 필요하지 않기 때문입니다.
따라서 이는 또 다른 단점도 가져옵니다. 메모리에 있는 대규모 데이터 세트를 사용하여 복잡한 계산을 수행하는 등 CPU 집약적인 작업을 사용해야 하는 경우 다른 프로세스의 작업이 차단됩니다. 마찬가지로, CPU 집약적인 작업이 포함된 원격 인터페이스 요청을 시작하면 실행해야 하는 다른 요청도 차단됩니다.
이 함수가 실행될 때까지 다음 함수가 실행될 수 있을 때까지 함수가 이벤트 루프 메커니즘을 차단하는 경우 차단 함수로 간주됩니다. 비차단 함수는 다음 함수 실행을 위해 이벤트 루프를 차단하지 않으며 콜백을 사용하여 작업이 완료되었음을 이벤트 루프에 알립니다.
모범 사례: 이벤트 루프를 차단하지 말고, 이벤트 루프를 지속적으로 실행하고, 동기 네트워크 인터페이스 호출이나 무한 루프와 같이 스레드를 차단하는 작업을 사용하지 않도록 주의하세요.
CPU 집약적 작업과 I/O(입력/출력) 집약적 작업을 구별하는 것이 중요합니다. 앞서 언급했듯이 Node.js는 여러 코드 조각을 동시에 실행하지 않습니다. I/O 작업만 비동기식이므로 동시에 실행됩니다.
따라서 작업자 스레드는 I/O 집약적인 작업에 그다지 도움이 되지 않습니다. 왜냐하면 비동기 I/O 작업이 작업자보다 효율적이기 때문입니다. 작업자의 주요 역할은 CPU 집약적인 작업의 성능을 향상시키는 것입니다.
또한 멀티 코어 CPU의 전체 활용을 보장하는 멀티 프로세스(클러스터 API) 솔루션과 같이 CPU 집약적인 작업을 위한 솔루션이 이미 많이 있습니다.
이 솔루션의 장점은 프로세스가 서로 독립적이라는 것입니다. 한 프로세스에서 문제가 발생하면 다른 프로세스에 영향을 미치지 않습니다. 또한 안정적인 API도 있지만 이는 메모리 공간을 공유할 수 없으며 프로세스 간 통신은 JSON 형식의 데이터를 통해서만 발생할 수 있음을 의미합니다.
그래서 스레드를 생성하고 동기화하는 Node.js 코어 모듈을 추가하면 CPU 집약적인 작업의 필요성이 해결될 것이라고 생각할 수도 있습니다.
하지만 멀티스레딩 모듈을 추가하면 언어 자체의 특성이 바뀌게 됩니다. 멀티스레딩 모듈을 사용 가능한 클래스나 함수로 추가하는 것은 불가능합니다. Java와 같이 멀티스레딩을 지원하는 일부 언어에서는 동기화 기능을 사용하여 여러 스레드 간의 동기화를 활성화합니다.
그리고 일부 숫자 유형은 충분히 원자적이지 않습니다. 즉, 동기식으로 작동하지 않으면 여러 스레드가 동시에 계산을 수행할 때 특정 값이 없으면 변수 값이 계속 변경될 수 있습니다. 변수 may 한 스레드에서 계산한 후 몇 바이트가 변경되고, 다른 스레드에서 계산한 후 다른 여러 바이트의 데이터가 변경됩니다. 예를 들어, JavaScript에서 0.1 + 0.2와 같은 간단한 계산의 결과는 17개의 십진수(소수의 가장 높은 자릿수)를 갖습니다.
var x = 0.1 + 0.2; // x will be 0.30000000000000004
하지만 부동 소수점 계산은 100% 정확하지 않습니다. 따라서 계산이 동기화되지 않으면 숫자의 소수 부분은 여러 스레드로 인해 정확한 숫자가 될 수 없습니다.
따라서 CPU 집약적인 작업의 성능 문제를 해결하는 방법은 작업자 스레드를 사용하는 것입니다. 브라우저에는 오랫동안 Workers 기능이 있었습니다.
단일 스레드 아래의 Node.js:
多线程 Workers 下 Node.js 拥有:
就像下图:
Worker_threads 模块允许使用多个线程来同时执行 JavaScript 代码。使用下面这个方式引入:
const worker = require('worker_threads');
Worker Threads 已经被添加到 Node.js 10 版本中,但是仍处于实验阶段。
使用 Worker threads 我们可以在在同一个进程内可以拥有多个 Node.js 实例,并且线程可以不需要跟随父进程的终止的时候才被终止,它可以在任意时刻被终止。当 Worker 线程销毁的时候分配给该 Worker 线程的资源依然没有被释放是一个很不好的操作,这会导致内存泄漏问题,我们也不希望这样。我们希望这些分配资源能够嵌入到 Node.js 中,让 Node.js 有创建线程的能力,并且在线程中创建一个新的 Node.js 实例,本质上就像是在同一个进程中运行多个独立的线程。
Worker Threads 有如下特性:
ArrayBuffers
可以将内存中的变量从一个线程转到另外一个SharedArrayBuffer
可以在多个线程中共享内存中的变量,但是限制为二进制格式的数据。可用的原子操作
,可以让你更有效率地同时执行某些操作并且实现竞态变量消息端口
,用于多个线程间通信。可以用于多个线程间传输结构化的数据,内存空间消息通道
就像多线程间的一个异步的双向通信通道。WorkerData
是用于传输启动数据。在多个线程间使用 postMessgae 进行传输的时候,数据会被克隆,并将克隆的数据传输到线程的 contructor 中。API:
const { worker, parantPort } = require('worker_threads');
=>worker
函数相当于一个独立的 JavaScript 运行环境线程,parentPort 是消息端口的一个实例new Worker(filename)
or new Worker(code, { eval: true })
=>启动 worker 的时候有两种方式,可以通过传输文件路径或者代码,在生产环境中推荐使用文件路径的方式。worker.on('message')
,worker.postMessage(data)
=> 这是多线程间监听事件与推送数据的方式。parentPort.on('message')
, parentPort.postMessage(data)
=> 在线程中使用 parentPort.postMessage
方式推送的数据可以在父进程中使用 worker.on('message')
的方式接收到,在父进程中使用 worker.postMessage()
的方式推送的数据可以在线程中使用 parentPort.on('message')
的方式监听到。const { Worker } = require('worker_threads'); const worker = new Worker(` const { parentPort } = require('worker_threads'); parentPort.once('message', message => parentPort.postMessage({ pong: message })); `, { eval: true }); worker.on('message', message => console.log(message)); worker.postMessage('ping');
$ node --experimental-worker test.js { pong: ‘ping’ }
上面例子所做的也就是使用 new Worker 创建一个线程,线程中的代码监听了 parentPort
的消息,并且当接收到数据的时候只触发一次回调,将收到的数据传输回父进程中。
你需要使用 --experimental-worker
启动程序因为 Workers 还在实验阶段。
另一个例子:
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); if (isMainThread) { module.exports = function parseJSAsync(script) { return new Promise((resolve, reject) => { const worker = new Worker(filename, { workerData: script }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); }); }; } else { const { parse } = require('some-js-parsing-library'); const script = workerData; parentPort.postMessage(parse(script)); }
上面代码中:
Worker
: 相当于一个独立的 JavaScirpt 运行线程。isMainThread
: 如果为 true 的话说明代码不是运行在 Worker 线程中parentPort
: 消息端口被使用来进行线程间通信workerData
:被传入 worker 的 contructor 的克隆数据。在实际使用中,应该使用线程池的方式,不然不断地创建 worker 线程的代价将会超过它带来的好处。
Chrome devTools는 Node.js에서 Workers 스레드 기능을 지원합니다. worker_threads
는 실험적인 모듈입니다. Node.js에서 CPU 집약적인 작업을 실행해야 하는 경우 현재 프로덕션 환경에서는 작업자 스레드를 사용하지 않는 것이 좋습니다. 대신 프로세스 풀을 사용할 수 있습니다.
영어 원본 주소: https://nodesource.com/blog/worker-threads-nodejs
저자: Liz Parody
더 많은 프로그래밍 관련 지식을 보시려면 Programming Video를 방문해 주세요! !
위 내용은 Node.js의 작업자 스레드에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!