Worker Threads — CPU 집약 작업 분리
"메인 프로세스에서 무거운 작업을 하면 IPC가 막히고, 렌더러에서 하면 UI가 얼어버린다" — Worker Threads나 UtilityProcess로 분리하면 해결됩니다.
세 가지 병렬 처리 방법
| 방법 | 환경 | 메모리 공유 | 용도 |
|---|---|---|---|
| Worker Threads | Node.js | SharedArrayBuffer | CPU 작업 |
| UtilityProcess | Electron | 없음 | Node.js 기능 필요 시 |
| Web Workers | 렌더러 | 없음 | UI 관련 병렬 작업 |
Worker Threads
// main.js에서 Worker Thread 생성
const { Worker } = require('worker_threads');
const path = require('path');
ipcMain.handle('process:heavy', async (_event, data) => {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(__dirname, 'workers/heavy.js'), {
workerData: data,
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker 종료 코드: ${code}`));
}
});
});
});
// workers/heavy.js
const { parentPort, workerData } = require('worker_threads');
// CPU 집약적 작업 수행
function processData(data) {
// 예: 대용량 JSON 파싱, 이미지 변환, 암호화 등
let result = 0;
for (let i = 0; i < data.iterations; i++) {
result += Math.sqrt(i);
}
return result;
}
const result = processData(workerData);
parentPort.postMessage(result);
Worker Pool
여러 작업을 처리할 때 Worker를 재사용하는 풀입니다.
class WorkerPool {
constructor(workerFile, poolSize = 4) {
this.workerFile = workerFile;
this.poolSize = poolSize;
this.workers = [];
this.queue = [];
for (let i = 0; i < poolSize; i++) {
this.workers.push({ worker: this.createWorker(), busy: false });
}
}
createWorker() {
return new Worker(this.workerFile);
}
execute(data) {
return new Promise((resolve, reject) => {
const available = this.workers.find(w => !w.busy);
if (available) {
this.runTask(available, data, resolve, reject);
} else {
this.queue.push({ data, resolve, reject });
}
});
}
runTask(entry, data, resolve, reject) {
entry.busy = true;
entry.worker.postMessage(data);
const onMessage = (result) => {
entry.busy = false;
entry.worker.removeListener('message', onMessage);
resolve(result);
this.processQueue();
};
entry.worker.on('message', onMessage);
entry.worker.once('error', reject);
}
processQueue() {
if (this.queue.length === 0) return;
const available = this.workers.find(w => !w.busy);
if (available) {
const { data, resolve, reject } = this.queue.shift();
this.runTask(available, data, resolve, reject);
}
}
destroy() {
this.workers.forEach(w => w.worker.terminate());
}
}
// 사용
const pool = new WorkerPool('./workers/heavy.js', 4);
const result = await pool.execute({ iterations: 1000000 });
UtilityProcess (Electron 22+)
// main.js
const { utilityProcess } = require('electron');
const child = utilityProcess.fork(path.join(__dirname, 'services/indexer.js'));
child.postMessage({ type: 'index', directory: '/path/to/docs' });
child.on('message', (msg) => {
if (msg.type === 'progress') {
mainWindow.webContents.send('index:progress', msg.percent);
}
if (msg.type === 'done') {
mainWindow.webContents.send('index:complete', msg.results);
}
});
// services/indexer.js
process.parentPort.on('message', async (e) => {
const { type, directory } = e.data;
if (type === 'index') {
const files = await scanDirectory(directory);
for (let i = 0; i < files.length; i++) {
await indexFile(files[i]);
process.parentPort.postMessage({
type: 'progress',
percent: Math.round((i / files.length) * 100),
});
}
process.parentPort.postMessage({ type: 'done', results: getIndex() });
}
});
Web Worker (렌더러)
// renderer에서 사용
const worker = new Worker('./renderer-worker.js');
worker.postMessage({ type: 'sort', data: largeArray });
worker.onmessage = (e) => {
const sorted = e.data;
renderTable(sorted);
};
면접 포인트 정리
- Worker Threads: Node.js 스레드, SharedArrayBuffer로 메모리 공유 가능
- UtilityProcess: Electron 전용, 별도 프로세스로 완전한 Node.js 환경
- Worker Pool 패턴으로 Worker 재사용하여 생성/파괴 비용 절감
- CPU 집약 작업은 반드시 별도 스레드/프로세스로 분리
Worker Threads를 다뤘으면, 다음은 플러그인 시스템을 알아봅시다.
댓글 로딩 중...