흔히 Node.js는 싱글 스레드 논-블로킹으로 작동한다고 알려져 있습니다. 싱글 스레드로 작동하되, 비동기 I/O 작업을 통하여 작업을 서로 블로킹하지 않음으로서 요청을 처리합니다.
그렇지만 Node.js에서 아예 멀티 스레드를 사용할 수 있는 방법이 없지는 않습니다. Node.js 12 LTS 버전부터 포함된 워커 스레드 패키지를 사용하면 멀티 스레드를 사용할 수 있습니다.
먼저, 워커 스레드를 생성하는 코드를 작성합니다. Worker
객체는 이벤트 기반으로 작동하지만, 메세지를 받았을 때 resolve를 하도록 작성합니다.
workerData에는 워커 스레드에 넘길 데이터를 입력합니다.
// index.ts
import path from 'node:path';
import { Worker } from 'node:worker_threads';
function bootstrap(): Promise<{}> {
return new Promise((resolve, reject) => {
const worker = new Worker(path.resolve(__dirname, 'service.js'), {
workerData: {
message: 'hello',
path: path.resolve(__dirname, 'service.ts'),
}
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
}
async function run() {
const message = await bootstrap();
console.log(message);
}
run().catch((err) => console.error(err));
워커 스레드를 생성할 때 타입스크립트 코드를 직접 읽을 수 있으면 좋겠으나… 타입스크립트 코드를 직접적으로 읽지 못합니다. 따라서 중간에서 타입스크립트 코드를 포함하는 자바스크립트 코드가 필요합니다.
워커 스레드를 생성하는 코드를 작성할 때 넘겨줄 데이터에 타입스크립트의 경로를 명시하였습니다. 받아온 경로를 이용하여 아래와 같이 자바스크립트 코드에 타입스크립트 코드를 포함시킵니다.
// service.js
const path = require('path');
const { workerData } = require('worker_threads');
require('ts-node').register();
require(path.resolve(__dirname, workerData.path));
이렇게 한 다음 워커 스레드에서 실행할 코드를 작성합니다. 간단한 메세지를 반환하는 코드를 작성합니다. 스레드를호출한 주체에 parentPort
를 통하여 메세지를 전송할 수 있습니다.
// service.ts
import { isMainThread, parentPort, workerData } from 'node:worker_threads';
function run() {
if (!isMainThread) {
parentPort!.postMessage(`${workerData.message}, world!`);
}
}
run();
워커 스레드를 실행하는 코드를 bootstrap
함수에 정의하였습니다. 스레드를 생성하고, 메세지를 받아오는 지 확인하기 위해 아래와 같이 작성하고 실행합니다.
// index.ts
// ... (생략)
async function run() {
const message = await bootstrap();
console.log(message);
}
run().catch((err) => console.error(err));