worker_threads 모듈
Node.js에서 멀티 스레딩 환경을 만들기 위해 사용하는 모듈
Node.js
이벤트 루프의 한계
JS의 메인 스레드는 한 번에 하나의 일만 처리한다. 이벤트 루프 모델은 시간이 걸리는 일들은 백그라운드에 위임하고 기다리지 않기 때문에 매우 효율적이다.
그러나 CPU 자체를 많이 사용해야 하는 무거운 계산 작업은 다른 곳에 위임할 수 없고, 메인 스레드가 직접 끝날 때까지 처리해야 한다.
이 작업이 실행되는 동안에는 다른 모든 작업(e.g. 사용자 클릭 반응, 화면 업데이트)이 멈추는 블로킹 현상이 발생한다. 이게 이벤트 루프의 한계다.
해결법
그래서 worker_threads를 사용한다.
- 별도의 작업자(스레드) 생성: 메인 스레드와는 완전히 독립적으로 동작하는 새로운 '워커 스레드'를 만든다.
- 무거운 작업 위임: CPU를 많이 쓰는 무거운 작업을 이 워커 스레드에게 통째로 맡긴다.
- 메인 스레드의 자유: 워커 스레드가 열심히 계산하는 동안, 메인 스레드는 그 작업에서 해방되어 원래 자신의 업무인 UI 반응이나 다른 가벼운 작업들을 멈춤 없이 계속 처리할 수 있다.
결론적으로 이벤트 루프를 대체하는 것은 아니다. 이벤트 루프의 약점인 CPU 집약적 작업으로 인한 메인 스레드 블로킹을 해결하여 병렬 처리를 할 수 있도록 보완해주는 강력한 도구다.
즉, 메인 스레드와 별도로 워커 스레드를 생성해 메인 스레드의 책임을 덜어주는 것과 같다.
사용법 예시
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 메인 스레드 로직: 워커를 생성하고 메시지를 보냅니다.
const worker = new Worker(__filename, {
workerData: "초기 데이터"
});
// 워커로부터 메시지 수신
worker.on('message', (msg) => {
console.log(`워커 스레드가 보낸 메시지: ${msg}`);
});
// 워커에게 메시지 전송
worker.postMessage('나는 메인 스레드야!');
} else {
// 워커 스레드 로직: 메인 스레드로부터 메시지를 받습니다.
console.log(`메인 스레드가 보낸 초기 데이터: ${workerData}`);
// 메인 스레드로부터 메시지 수신
parentPort.on('message', (msg) => {
console.log(`메인 스레드가 보낸 메시지: ${msg}`);
// 메인 스레드에게 메시지 전송
parentPort.postMessage('나는 워커 스레드야!');
parentPort.close(); // 작업 완료 후 종료
});
}
-
Worker클래스: 메인 스레드에서 새로운 워커 스레드를 생성할 때 사용한다. 생성자의 첫 번째 인자로 워커 스레드에서 실행할 파일 경로를 전달한다. -
isMainThread: 현재 코드가 메인 스레드에서 실행되는지, 워커 스레드에서 실행되는지를 판별하는boolean값 일반적으로 이 값을 기준으로 메인 스레드와 워커 스레드의 로직을 분기한다. -
parentPort: 워커 스레드에서 메인 스레드와 통신하기 위해 사용하는 객체 -
postMessage(): 스레드 간에 메시지(데이터)를 전송하는 메서드 메인 스레드에서는worker객체를 통해, 워커 스레드에서는parentPort객체를 통해 호출 -
workerData: 워커 스레드를 생성할 때 초기 데이터를 전달하기 위해 사용되는 옵션입니다.
스레드 간 통신은 이벤트 기반으로 이루어 진다.
-
메인 스레드 → 워커 스레드:
worker.postMessage()로 메시지를 보내고, 워커 스레드에서는parentPort.on('message', ...)로 메시지를 받는다. -
워커 스레드 → 메인 스레드:
parentPort.postMessage()로 메시지를 보내고, 메인 스레드에서는worker.on('message', ...)로 메시지를 받는다.