Web Worker는 페이지의 메인 스레드와 별개인 백그라운드 스레드에서 자바스크립트를 돌리는 브라우저 기능이다. 클릭에 반응하는 스크립트에 끼어들지 않고 긴 계산을 돌릴 수 있어서, 무거운 일을 하는 동안에도 페이지가 응답한다.
메인 스레드는 이벤트 루프 하나로 입력·렌더링·스크립트를 번갈아 처리한다. 긴 계산은 비동기로 바꿔도 같은 스레드에서 돌아서 루프를 붙잡는다(동기·비동기와 블로킹·논블로킹). 마이크로태스크로 잘게 나눠도 양보가 되지 않는다. 계산 자체가 무거우면 Worker로 옮긴다.
기본 사용법
new Worker(url, options)로 만들고, 양쪽이 postMessage()로 보내고 onmessage로 받는다. options.type을 "module"로 주면 Worker 스크립트를 ES 모듈로 실행해 import를 쓸 수 있다(기본값은 "classic").
// main.ts
const worker = new Worker("/sum.worker.js", { type: "module" });
worker.onmessage = (event: MessageEvent<number>) => {
output.textContent = String(event.data); // 화면은 메인 스레드가 바꾼다
};
const data = new Float64Array(10_000_000).map(Math.random);
// postMessage(message, transfer): transfer에 넣은 버퍼는 복사 없이 소유권이 넘어간다
worker.postMessage(data, [data.buffer]);
console.log(data.byteLength); // 0, 보낸 쪽 버퍼는 분리(detached)되었다
// sum.worker.ts (빌드되어 /sum.worker.js로 제공된다고 가정)
self.onmessage = (event: MessageEvent<Float64Array>) => {
let sum = 0;
for (const x of event.data) sum += x;
self.postMessage(sum);
};- Worker 안에서는 DOM을 직접 건드릴 수 없고
window의 일부 메서드·속성도 쓸 수 없다. 결과를 메시지로 돌려보내고 메인 스레드가 화면을 바꾼다 fetch()나XMLHttpRequest로 네트워크 요청은 할 수 있다- 메인 스레드에서
worker.terminate()를 부르면 Worker 스레드가 바로 종료된다
복사와 소유권 이전
postMessage로 보낸 데이터는 공유되지 않고 복사된다. 브라우저는 구조화된 복제(structured clone) 알고리즘으로 보낸 쪽에서 직렬화하고 받는 쪽에서 되살린다. 순환 참조처럼 JSON이 못 담는 값도 담는다- 함수와 DOM 노드는 복제할 수 없어
DataCloneError가 난다. 프로토타입 체인과 getter·setter, 클래스 private 필드도 복제되지 않는다. 클래스 인스턴스는 메서드 없는 평범한 객체로 도착한다(깊은 복사와 비슷하지만 더 좁다) - 큰 데이터는 전송 가능 객체(transferable object)로 넘긴다.
ArrayBuffer,MessagePort,ImageBitmap,OffscreenCanvas등이 있다. 자원이 한 번에 한 쪽에만 있도록 소유권을 옮기는 방식이라 복사 비용이 없다. 대신 보낸 쪽 객체는 더 쓸 수 없고 읽거나 쓰면 예외가 난다 Uint8Array같은 형식화 배열은 그 자체가 아니라 밑의.buffer를 transfer 목록에 넣는다
종류
| 종류 | 누가 쓰나 | 특징 |
|---|---|---|
Dedicated Worker (Worker) | 만든 스크립트 하나 | 가장 흔한 형태 |
Shared Worker (SharedWorker) | 출처(origin)가 같은 여러 창·iframe | port 객체로 통신한다 |
| Service Worker | 등록 범위(scope) 안의 페이지 | 앱과 네트워크 사이의 프록시 |
Service Worker도 Worker 문맥에서 돌아 DOM이 없지만 목적이 다르다. 계산을 옮기는 용도가 아니라 네트워크 요청을 가로채 오프라인 경험이나 푸시 알림을 만드는 별개 API다. HTTPS 같은 보안 문맥에서만 쓸 수 있다. API 모킹 도구인 MSW로 API 모킹가 이 기능을 쓴다.
언제 쓰나
- 이미지·영상 처리, 큰 JSON 파싱, 압축·해시처럼 CPU 바운드 계산이 메인 스레드를 오래 붙잡아 INP가 나빠질 때
- Worker 하나로 옮긴 계산은 같은 시간이 걸린다. 얻는 것은 메인 스레드의 응답성이다. 전체 시간을 줄이려면 작업을 쪼개 여러 Worker에 나눈다
- 명세는 Worker가 비교적 무거워서 많이 띄우는 용도가 아니라고 말한다. 시작 비용과 인스턴스당 메모리 비용이 크다는 것이다. 픽셀마다 Worker를 하나씩 만드는 식은 맞지 않고, 오래 살려 두고 재사용하는 쪽이 낫다
- 그래서 금방 끝나는 계산을 매번 새 Worker로 보내면 시작·복사 비용이 이득보다 클 수 있다.
fetch처럼 기다리는 일은 옮겨도 빨라지지 않는다
명세: HTML Standard — Web workers · Dedicated workers and the Worker interface · Shared workers and the SharedWorker interface
출처: MDN — Using Web Workers · MDN — Worker() constructor · MDN — Transferable objects · MDN — The structured clone algorithm · MDN — Service Worker API · MDN — SharedWorker