
JS의 메인 스레드는 한 번에 하나의 일만 처리한다.
이벤트 루프 모델은 시간이 걸리는 일들은 백그라운드에 위임하고 기다리지 않기 때문에 매우 효율적이다.
그러나 CPU 자체를 많이 사용해야 하는 무거운 계산 작업은 다른 곳에 위임할 수 없고, 메인 스레드가 직접 끝날 때까지 처리해야 한다.
이 작업이 실행되는 동안에는 다른 모든 작업(e.g. 사용자 클릭 반응, 화면 업데이트)이 멈추는 블로킹 현상이 발생한다. 이게 이벤트 루프의 한계다.
그래서 worker_threads를 사용한다.
결론적으로 이벤트 루프를 대체하는 것은 아니다.
이벤트 루프의 약점인 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', ...)로 메시지를 받는다.