Web Worker란?
Web Worker는 브라우저에서 JavaScript를 백그라운드 스레드에서 실행할 수 있게 해주는 API입니다. 메인 스레드와 독립적으로 동작하여, 무거운 연산 중에도 UI가 멈추지 않습니다. Worker는 DOM에 접근할 수 없으며, postMessage를 통해 메인 스레드와 데이터를 주고받습니다.
기본 사용법
// worker.js
self.addEventListener('message', (e) => {
const { type, data } = e.data;
if (type === 'HEAVY_CALC') {
let result = 0;
for (let i = 0; i < data.iterations; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
self.postMessage({ type: 'RESULT', result });
}
});
// main.js
const worker = new Worker('worker.js');
worker.addEventListener('message', (e) => {
console.log('결과:', e.data.result);
});
worker.postMessage({
type: 'HEAVY_CALC',
data: { iterations: 100_000_000 }
});
Transferable Objects로 성능 향상
// 대용량 데이터 전송 시 복사 대신 소유권 이전
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
// 느림: 데이터를 복사
// worker.postMessage({ buffer });
// 빠름: 소유권을 이전 (zero-copy)
worker.postMessage({ buffer }, [buffer]);
// 이후 메인 스레드에서 buffer는 사용 불가
이미지 처리 실전 예제
// image-worker.js
self.addEventListener('message', async (e) => {
const { imageData, filter } = e.data;
const pixels = imageData.data;
if (filter === 'grayscale') {
for (let i = 0; i < pixels.length; i += 4) {
const avg = (pixels[i] + pixels[i+1] + pixels[i+2]) / 3;
pixels[i] = pixels[i+1] = pixels[i+2] = avg;
}
} else if (filter === 'blur') {
const w = imageData.width;
const h = imageData.height;
const copy = new Uint8ClampedArray(pixels);
for (let y = 1; y < h - 1; y++) {
for (let x = 1; x < w - 1; x++) {
for (let c = 0; c < 3; c++) {
let sum = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
sum += copy[((y+dy)*w + (x+dx))*4 + c];
}
}
pixels[(y*w + x)*4 + c] = sum / 9;
}
}
}
}
self.postMessage({ imageData }, [imageData.data.buffer]);
});
Worker Pool 패턴
class WorkerPool {
constructor(workerUrl, size = navigator.hardwareConcurrency || 4) {
this.workers = Array.from({ length: size }, () => new Worker(workerUrl));
this.queue = [];
this.freeWorkers = [...this.workers];
}
exec(data) {
return new Promise((resolve) => {
const task = { data, resolve };
const worker = this.freeWorkers.pop();
if (worker) {
this._run(worker, task);
} else {
this.queue.push(task);
}
});
}
_run(worker, task) {
worker.onmessage = (e) => {
task.resolve(e.data);
const next = this.queue.shift();
if (next) this._run(worker, next);
else this.freeWorkers.push(worker);
};
worker.postMessage(task.data);
}
terminate() {
this.workers.forEach(w => w.terminate());
}
}
// 사용 예
const pool = new WorkerPool('worker.js', 4);
const results = await Promise.all(
chunks.map(chunk => pool.exec(chunk))
);
Worker 사용 시 주의사항
- Worker 생성 비용이 있으므로, 반복 생성보다 재사용(Worker Pool)이 효율적입니다
- DOM 접근 불가 —
document,window객체를 사용할 수 없습니다 SharedArrayBuffer를 사용하면 Worker 간 메모리 공유가 가능합니다 (COOP/COEP 헤더 필요)- Webpack에서는
new Worker(new URL('./worker.js', import.meta.url))구문을 사용합니다 - 에러 처리를 위해
worker.onerror핸들러를 반드시 등록하세요
댓글 0