우리 모두는 Node.js가 단일 스레드, 이벤트 기반 비동기 I/O 모델을 사용한다는 것을 알고 있습니다. 그 특성상 멀티 코어 CPU를 활용할 수 없으며 일부 비 I/O 작업을 완료하는 데 좋지 않습니다. 스크립트 실행, AI 컴퓨팅, 이미지 처리 등), 이러한 문제를 해결하기 위해 Node.js는 기존의 멀티 프로세스(스레드) 솔루션을 제공합니다(프로세스 및 스레드에 대한 논의는 저자의 다른 기사 Node.js 및 동시성 모델), 이 기사에서는 Node.js의 다중 스레드 메커니즘을 소개합니다.
child_process
는 node.js의 하위 프로세스를 생성하여 일부 특수 작업(예: 스크립트 실행)을 완료할 수 있습니다. 이 모듈은 주로 exec
, execFile
, fork
, spwan
및 기타 메서드를 제공합니다. . 사용.
const { exec } = require('child_process'); exec('ls -al', (오류, stdout, stderr) => { console.log(stdout); });
이 메서드는 options.shell
에 지정된 실행 파일에 따라 명령 문자열을 처리하고 명령 실행 중에 출력을 캐시한 다음 명령 실행이 완료될 때까지 실행 결과를 콜백 함수 매개 변수 형식으로 반환합니다.
이 메소드의 매개변수는 다음과 같이 설명됩니다:
command
: 실행될 명령(예: ls -al
);
options
: 매개변수 설정(선택 사항), 관련 속성은 다음과 같습니다:
cwd
: 하위 프로세스의 현재 작업 디렉터리 , 기본값은 process.cwd()
값입니다.
shell
env
쌍 개체), 기본값은 process.env
값입니다.
encoding
: 문자 인코딩, 기본값은 utf8
;
명령 문자열을 처리하는 파일, Unix
의 기본값은 /bin/sh
이고 Windows
의 기본값은 process.env.ComSpec
값입니다(비어 있는 경우 cmd.exe
). 예:
const { exec } = require('child_process'); exec("print('Hello World!')", { 쉘: 'python' }, (error, stdout, stderr) => { console.log(stdout); });
위의 예제를 실행하면 python -c "print('Hello World!')"
명령을 실행하는 하위 프로세스와 동일한 Hello World!
출력됩니다. 따라서 이 속성을 사용할 때 주의해야 합니다. -c
옵션을 통한 관련문 실행이 지원되어야 합니다.
참고: Node.js
도 -c
옵션을 지원하지만 이는 --check
옵션과 동일합니다. 이 옵션은 지정된 스크립트에 구문 오류가 있는지 여부를 감지하는 데만 사용되며 관련 스크립트를 실행하지 않습니다.
signal
: 지정된 AbortSignal을 사용하여 하위 프로세스를 종료합니다. 이 속성은 v14.17.0 이상에서 사용할 수 있습니다. 예:
const { exec } = require('child_process'); const ac = 새로운 AbortController(); exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});
위의 예에서는 ac.abort()
호출하여 하위 프로세스를 조기에 종료할 수 있습니다.
timeout
: 자식 프로세스의 시간 초과 시간(이 속성의 값이 0
보다 큰 경우 자식 프로세스의 실행 시간이 지정된 값을 초과하면 killSignal
속성에 지정된 종료 신호가 자식 프로세스로 전송됩니다. ), 기본값은 0
입니다.
killSignal
maxBuffer
1024 * 1024
: 하위 프로세스 종료 신호, 기본값은 SIGTERM
;
uid
: 하위 프로세스 실행을 위한 uid
;
gid
: 하위 프로세스 실행을 위한 gid
;
windowsHide
: Windows
시스템에서 일반적으로 사용되는 하위 프로세스의 콘솔 창을 숨길지 여부 기본값은 false
입니다.
callback
: error
, stdout
, stderr
을 포함한 콜백 함수:
error
: 명령줄이 성공적으로 실행되면 값은 null
이고, 그렇지 않으면 값은 Error의 인스턴스입니다. 여기서 error.code
는 종료입니다. 자식 프로세스의 오류 코드, error.signal
자식 프로세스의 종료 신호입니다.stdout
및 stderr
: child 프로세스의 stdout
및 stderr
encoding
encoding
이 buffer
인 경우 인코딩됩니다. , 또는 stdout
또는 stderr
값이 인식할 수 없는 문자열인 경우 buffer
에 따라 인코딩됩니다.const { execFile } = require('child_process'); execFile('ls', ['-al'], (오류, stdout, stderr) => { console.log(stdout); });
이 메소드의 기능은 exec
와 유사합니다. 유일한 차이점은 execFile
기본적으로 지정된 실행 파일(즉, 매개변수 file
의 값)을 사용하여 명령을 직접 처리하므로 효율성이 exec
보다 약간 높다는 것입니다. (쉘의 처리 로직을 보면 효율성이 미미하다고 느껴집니다.)
이 메소드의 매개변수에 대한 설명은 다음과 같습니다.
file
: 실행 파일의 이름 또는 경로
args
: 실행 파일의 매개변수 목록
shell
options
매개변수 설정(지정할 수 없음), 관련 속성은 다음과 같습니다.
false
인 경우 지정된 실행 파일(즉, 매개변수 file
의 값)을 직접 사용하여 명령을 처리한다는 의미입니다. 값이 true
이거나 다른 문자열인 경우 해당 함수는 exec
의 shell
과 동일합니다. 기본값은 false
입니다.maxBuffer
timeout
gid
uid
killSignal
cwd
env
encoding
windowsVerbatimArguments
Windows
Unix
false
입니다.windowsHide
및 signal
위에서 소개되었으므로 여기서는 반복하지 않겠습니다.callback
: 콜백 함수로 exec
의 callback
과 동일하므로 여기서는 설명하지 않습니다.
const { 포크 } = require('child_process'); const echo = 포크('./echo.js', { 침묵: 사실 }); echo.stdout.on('data', (데이터) => { console.log(`stdout: ${data}`); }); echo.stderr.on('data', (데이터) => { console.error(`stderr: ${data}`); }); echo.on('닫기', (코드) => { console.log(`하위 프로세스가 ${code} 코드로 종료되었습니다`); });
이 메서드는 지정된 Node.js 스크립트를 실행하고 IPC를 통해 상위 프로세스와 통신하기 위해 새 Node.js 인스턴스를 만드는 데 사용됩니다.
이 메소드의 매개변수에 대한 설명은 다음과 같습니다.
modulePath
: 실행할 Node.js 스크립트의 경로,
args
: Node.js 스크립트에 전달되는 매개변수 목록
options
: 매개변수 설정(지정할 수 없음), 관련 속성 예:
detached
: spwan
에 대한 설명 options.detached
execPath
: 하위 프로세스의 실행 파일을 생성합니다.
serialization
execArgv
된 문자열 매개변수 목록, 기본값은 process.execArgv
의 값입니다.
: 프로세스 간 메시지의 일련 번호 유형, 사용 가능한 값은 json
및 advanced
이며 기본값은 json
입니다.
true
slient
경우 하위 프로세스의 stdin
, stdout
및 stderr
상위 프로세스로 전달됩니다. 그렇지 않으면 상위 프로세스의 stdin
, stdout
및 stderr
이 상속됩니다. 기본값은 false
입니다.
spwan
stdio
options.stdio
설명을 참조하세요. 여기서 주목해야 할 점은
slient
값이ipc
값을 가진 옵션(예: [0, 1, 2, 'ipc']
)을 포함해야 한다는 것입니다. 예외가 발생합니다.cwd
, env
, uid
, gid
, windowsVerbatimArguments
, signal
, timeout
및 killSignal
속성은 위에서 소개되었으며 여기서는 반복되지 않습니다.
const {spawn } = require('child_process'); const ls = generate('ls', ['-al']); ls.stdout.on('data', (데이터) => { console.log(`stdout: ${data}`); }); ls.stderr.on('데이터', (데이터) => { console.error(`stderr: ${data}`); }); ls.on('닫기', (코드) => { console.log(`하위 프로세스가 ${code} 코드로 종료되었습니다`); });
이 메서드는 child_process
모듈의 기본 메서드입니다. exec
, execFile
및 fork
결국 자식 프로세스를 생성하기 위해 spawn
호출합니다.
이 메소드의 매개변수에 대한 설명은 다음과 같습니다.
command
: 실행 파일의 이름 또는 경로
args
: 실행 파일에 전달되는 매개변수 목록
options
: 매개변수 설정(지정할 수 없음), 관련 속성은 다음과 같습니다.
argv0
: 자식 프로세스에 전송된 argv[0 ] 값, 기본값은 command
매개변수의 값입니다.
detached
: 자식 프로세스가 부모 프로세스와 독립적으로 실행되도록 허용할지 여부(즉, 부모 프로세스가 종료된 후 자식 프로세스는 계속 실행 가능), 기본값은 false
이고 값이 true
이면 각 플랫폼에 미치는 영향은 다음과 같습니다.
Windows
시스템에서는 상위 프로세스가 종료된 후에도 하위 프로세스가 계속 실행될 수 있으며 하위 프로세스는 자체 콘솔 창이 있습니다.(이 기능이 시작되면 실행 중인 프로세스 중에 변경할 수 없습니다.)Windows
프로세스는 이때 관계없이 새 프로세스 세션 그룹의 리더 역할을 합니다. 자식 프로세스가 부모 프로세스와 분리되어 있는지 여부에 따라 부모 프로세스가 종료된 후에도 자식 프로세스가 계속 실행될 수 있습니다.자식 프로세스가 장기적인 작업을 수행해야 하고 부모 프로세스가 일찍 종료되기를 원하는 경우 다음 사항을 동시에 충족해야 합니다.
unref
메서드를 호출하여 자식을 제거합니다. 상위 프로세스의 이벤트 루프에서 프로세스가 분리되었습니다.detached
true
설정하면ignore
stdio
.예를 들어 다음 예는 다음과 같습니다.
// hello.js const fs = require('fs'); 인덱스 = 0으로 놔두세요; 함수 실행() { setTimeout(() => { fs.writeFileSync('./hello', `index: ${index}`); if (색인 < 10) { 인덱스 += 1; 달리다(); } }, 1000); } 달리다(); // 메인.js const {spawn } = require('child_process'); const child = generate('node', ['./hello.js'], { 분리됨: 사실, stdio: '무시' }); child.unref();
stdio
: 하위 프로세스 표준 입력 및 출력 구성, 기본값은 pipe
, 값은 문자열 또는 배열입니다.
pipe
['pipe', 'pipe', 'pipe']
)로 변환되며 사용 가능한 값은 pipe
, overlapped
, ignore
, inherit
입니다.stdin
, stdout
및 stderr
각각의 구성, 각각 항목의 사용 가능한 값은 pipe
, overlapped
, ignore
, inherit
, ipc
, Stream 객체, 양의 정수(상위 프로세스에서 열린 파일 설명자), null
(있는 경우) 배열의 처음 세 항목에 있는 경우에는 pipe
와 동일하고, 그렇지 않은 경우에는 ignore
와 동일합니다. ), undefined
(배열의 처음 세 항목에 있는 경우에는 pipe
와 동일하며, 그렇지 않으면 다음과 같습니다. ignore
).cwd
, env
, uid
, gid
, serialization
, shell
(값은 boolean
또는 string
), windowsVerbatimArguments
, windowsHide
, signal
, timeout
, killSignal
속성은 위에서 소개되었으며 여기서는 반복되지 않습니다.
위는 child_process
모듈의 주요 메소드 사용에 대해 간략하게 소개합니다. execSync
, execFileSync
, forkSync
및 spwanSync
메소드는 exec
, execFile
및 spwan
의 동기 버전이므로 매개변수에는 차이가 없습니다. 그들은 반복되지 않을 것입니다.
cluster
모듈을 통해 Node.js 프로세스 클러스터를 클러스터에 추가하면 멀티 코어의 장점을 최대한 활용하고 프로그램 작업을 다른 프로세스에 분산하여 실행을 향상시킬 수 있습니다. 아래에서는 프로그램의 효율성을 높이겠습니다. 이 예에서는 cluster
모듈의 사용을 소개합니다.
const http = require('http'); const 클러스터 = require('클러스터'); const numCPUs = require('os').cpus().length; if (cluster.isPrimary) { for (let i = 0; i < CPU 수; i++) { 클러스터.포크(); } } 또 다른 { http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); }).listen(8000); }
위의 예는 cluster.isPrimary
속성의 판단(즉, 현재 프로세스가 메인 프로세스인지 판단하는 것)을 기준으로 두 부분으로 나누어집니다.
cluster.fork
호출을 통해8000
)에서 수신 대기합니다.위의 예제를 실행하고 브라우저에서 http://localhost:8000/
에 액세스하면 반환된 pid
액세스마다 다르며, 이는 요청이 실제로 각 하위 프로세스에 배포되었음을 알 수 있습니다. Node.js에서 채택한 기본 로드 밸런싱 전략은 라운드 로빈 스케줄링이며 환경 변수 NODE_CLUSTER_SCHED_POLICY
또는 cluster.schedulingPolicy
속성을 통해 수정할 수 있습니다.
NODE_CLUSTER_SCHED_POLICY = rr // 또는 없음 Cluster.schedulingPolicy = Cluster.SCHED_RR; // 또는 Cluster.SCHED_NONE
주목해야 할 또 다른 점은 각 하위 프로세스가 HTTP 서버를 생성하고 동일한 포트를 수신한다고 해서 이러한 하위 프로세스가 자유롭게 경쟁할 수 있다는 의미는 아닙니다. 이는 모든 하위 프로세스의 로드 균형을 보장할 수 없기 때문입니다. 따라서 올바른 프로세스는 기본 프로세스가 포트를 수신한 다음 배포 정책에 따라 처리할 수 있도록 사용자 요청을 특정 하위 프로세스로 전달하는 것입니다.
프로세스는 서로 격리되어 있으므로 일반적으로 프로세스는 공유 메모리, 메시지 전달, 파이프와 같은 메커니즘을 통해 통신합니다. Node.js는 다음 예와 같이消息传递
통해 상위 프로세스와 하위 프로세스 간의 통신을 완료합니다.
const http = require('http'); const 클러스터 = require('클러스터'); const numCPUs = require('os').cpus().length; if (cluster.isPrimary) { for (let i = 0; i < CPU 수; i++) { const 작업자 = Cluster.fork(); 작업자.on('메시지', (메시지) => { console.log(`나는 기본(${process.pid})입니다. 작업자로부터 메시지를 받았습니다: "${message}"`); Worker.send(`작업자에게 메시지 보내기`) }); } } 또 다른 { process.on('메시지', (메시지) => { console.log(`나는 작업자(${process.pid})입니다. 기본 메시지: "${message}"`) }); http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); process.send('기본으로 메시지 보내기'); }).listen(8000); }
위의 예제를 실행하고 http://localhost:8000/
방문한 후 터미널을 확인하면 다음과 유사한 출력이 표시됩니다.
나는 기본(44460)입니다. 작업자로부터 "기본으로 메시지 보내기"라는 메시지를 받았습니다. 저는 작업자(44461)입니다. 기본 서버로부터 "작업자에게 메시지 보내기"라는 메시지를 받았습니다. 저는 기본(44460)입니다. 작업자로부터 "기본으로 메시지 보내기"라는 메시지를 받았습니다. 저는 작업자(44462)입니다. 기본 메시지를 받았습니다: "작업자에게 메시지 보내기"
이 메커니즘을 사용하면 각 하위 프로세스의 상태를 모니터링하여 하위 프로세스에 사고가 발생할 경우 적시에 개입할 수 있습니다. 서비스 가용성을 보장하기 위해.
cluster
모듈의 인터페이스는 매우 간단합니다. 공간을 절약하기 위해 여기에서는 cluster.setupPrimary
메서드에 대한 몇 가지 특별한 설명만 작성합니다. 다른 방법에 대해서는 공식 문서를 확인하세요.
cluster.setupPrimary
호출된 후 관련 설정 cluster.settings
속성에 동기화되며 모든 각 호출은 현재 cluster.settings
속성 값을 기반으로 합니다.cluster.setupPrimary
호출된 후에는 실행 중인 하위 프로세스에 영향을 주지 않으며 후속 cluster.fork
호출에만 영향을 미칩니다.cluster.setupPrimary
는 cluster.fork
에 대한 후속 전달에 영향을 주지 않습니다. 호출의 env
매개변수는cluster.setupPrimary
프로세스에서만 사용할 수 있습니다.이전에 Node.js 프로세스 클러스터를 생성하여 프로그램 실행 효율성을 향상시킬 수 있는 cluster
모듈을 소개했습니다. 그러나 cluster
프로세스 간 전환 및 격리 비용이 높은 다중 프로세스 모델을 기반으로 합니다. 프로세스 간 리소스 부족으로 인해 자식 프로세스 수가 증가하면 시스템 리소스 제약으로 인해 응답하지 못하는 문제가 발생하기 쉽습니다. 이러한 문제를 해결하기 위해 Node.js는 worker_threads
제공합니다. 아래에서는 구체적인 예를 통해 이 모듈의 사용법을 간략하게 소개합니다.
// server.js const http = require('http'); const { 작업자 } = require('worker_threads'); http.createServer((req, res) => { const httpWorker = new Worker('./http_worker.js'); httpWorker.on('메시지', (결과) => { res.writeHead(200); res.end(`${result}n`); }); httpWorker.postMessage('톰'); }).listen(8000); // http_worker.js const { parentPort } = require('worker_threads'); parentPort.on('메시지', (이름) => { parentPort.postMessage(`${name}님을 환영합니다!`); });
위의 예는 worker_threads
의 간단한 사용을 보여줍니다. worker_threads
사용할 때 다음 사항에 주의해야 합니다.
worker_threads.Worker
스크립트는 독립적인 JavaScript
파일이거나字符串
일 수 있습니다. 예를 들어, 위의 예는 다음과 같이 수정될 수 있습니다:
const code = "const { parentPort } = require('worker_threads'); parentPort.on('message', (name) => {parentPort.postMessage(`Welcone ${ 이름}!` );})"; const httpWorker = new Worker(code, { eval: true });
다음
worker_threads.Worker
같이 workerData
값을 지정하여 Worker 하위 스레드의 초기 메타데이터를 설정할 수 있습니다.
.js const { 작업자 } = require('worker_threads'); const httpWorker = new Worker('./http_worker.js', { WorkerData: { 이름: 'Tom'} }); // http_worker.js const { WorkerData } = require('worker_threads'); console.log(workerData);
worker_threads.Worker
통해 Worker 인스턴스를 생성할 때 SHARE_ENV
설정하여 Worker 하위 스레드와 메인 스레드 간에 환경 변수를 공유해야 한다는 사실을 인식할 수 있습니다. 예를 들면 다음과 같습니다.
const { Worker, SHARE_ENV } = require('worker_threads'); const 작업자 = new Worker('process.env.SET_IN_WORKER = "foo"', { eval: true, env: SHARE_ENV }); 작업자.on('종료', () => { console.log(process.env.SET_IN_WORKER); });
cluster
의 프로세스 간 통신 메커니즘과 달리 worker_threads
MessageChannel을 사용하여 스레드 간 통신합니다.
parentPort.postMessage
메서드를 통해 기본 스레드에 메시지를 보내고, message
수신하여 기본 스레드의 메시지를 처리합니다. parentPort
메시지의 message
이벤트는httpWorker
이며 아래 Worker 하위 스레드로 대체됨)의 postMessage
메서드를 통해 httpWorker
에 메시지를 보내고 Worker 하위 스레드의 메시지를 처리합니다. httpWorker
의 message
이벤트를 수신합니다.Node.js에서는 cluster
에 의해 생성된 자식 프로세스이든, worker_threads
에 의해 생성된 Worker 자식 스레드이든 모두 자체 V8 인스턴스와 이벤트 루프를 가지고 있습니다. 차이점은
Worker 하위 스레드가 하위 프로세스보다 더 효율적인 것처럼 보이지만 Worker 하위 스레드에도 단점이 있습니다. 즉, cluster
로드 밸런싱을 제공하는 반면 worker_threads
로드 밸런싱의 설계와 구현을 우리가 직접 완료해야 합니다.
이 기사에서는 Node.js에서 세 가지 모듈 child_process
, cluster
및 worker_threads
의 사용을 소개합니다. 이 세 가지 모듈을 통해 멀티 코어 CPU의 장점을 최대한 활용하고 멀티 스레드의 일부 특별한 문제를 효율적으로 해결할 수 있습니다. 스레드) 모드 작업(예: AI, 이미지 처리 등)의 운영 효율성입니다. 각 모듈에는 적용 가능한 시나리오가 있습니다. 이 문서에서는 기본 사용법만 설명합니다. 자신의 문제에 따라 효율적으로 사용하는 방법은 아직 직접 탐색해야 합니다. 마지막으로, 이 글에 잘못된 부분이 있다면 바로잡아주시길 바랍니다. 모두들 행복한 코딩생활 되시기 바랍니다.