5.7 KiB
5.7 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| web-offmain-webworker | Web Worker / Comlink — main thread 비우기 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Web Worker
Browser 도 main thread block = INP / jank. 무거운 JS = Web Worker. Comlink 가 RPC wrap. OffscreenCanvas 가 worker 에서 그림. SharedArrayBuffer 가 zero-copy.
📖 핵심 개념
- Web Worker: 별 thread, postMessage 통신.
- Service Worker: 다름 — 네트워크 인터셉트 (위 PWA).
- Shared Worker: 여러 tab 공유.
- OffscreenCanvas: 캔버스 그림 worker 에서.
💻 코드 패턴
단순 worker
// worker.ts
self.onmessage = (e) => {
const { data } = e;
const result = heavyComputation(data);
self.postMessage(result);
};
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
worker.onmessage = (e) => console.log('result:', e.data);
worker.postMessage({ items: [...] });
Comlink (RPC wrap)
// worker.ts
import * as Comlink from 'comlink';
class HeavyAPI {
async process(items: number[]): Promise<number> {
return items.reduce((s, x) => s + x * x, 0);
}
async fft(samples: Float32Array): Promise<Float32Array> { ... }
}
Comlink.expose(new HeavyAPI());
// main.ts
import * as Comlink from 'comlink';
import type { HeavyAPI } from './worker';
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
const api = Comlink.wrap<HeavyAPI>(worker);
const sum = await api.process([1, 2, 3]);
→ promise 기반 RPC, type-safe.
Vite worker import
import HeavyWorker from './worker?worker';
const worker = new HeavyWorker();
Transferable (zero-copy)
const buf = new ArrayBuffer(10_000_000);
worker.postMessage({ buf }, [buf]);
// buf 는 main 에서 더 사용 못 함 — transferred
→ 큰 데이터 빠르게.
SharedArrayBuffer (양방향 공유)
// Cross-origin isolation 필요
// Response headers:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp
const sab = new SharedArrayBuffer(1024);
const view = new Int32Array(sab);
worker.postMessage({ sab });
// worker 와 main 이 같은 메모리
Atomics.store(view, 0, 42);
const v = Atomics.load(view, 0);
Atomics.wait(view, 0, 0); // block 까지 변경
Atomics.notify(view, 0, 1); // 깨우기
OffscreenCanvas
// main
const canvas = document.getElementById('c') as HTMLCanvasElement;
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
// worker
self.onmessage = (e) => {
const ctx = e.data.canvas.getContext('2d');
// 그림 — main thread 영향 X
};
→ 게임 / 차트 / 이미지 처리.
Use case
- 큰 JSON parse
- 이미지 / 비디오 처리 (Sharp, FFmpeg.js)
- 압축 / 암호화 (gzip, AES)
- ML inference (ONNX Runtime Web, TF.js)
- PDF generation
- Crypto / hashing
React 통합
function useWorker<T, R>(input: T): R | null {
const [result, setResult] = useState<R | null>(null);
useEffect(() => {
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
worker.onmessage = (e) => setResult(e.data);
worker.postMessage(input);
return () => worker.terminate();
}, [input]);
return result;
}
또는 Comlink + useEffect:
const apiRef = useRef<Remote<HeavyAPI>>();
useEffect(() => {
const w = new Worker(...);
apiRef.current = Comlink.wrap(w);
return () => w.terminate();
}, []);
Pool (여러 worker 병렬)
class WorkerPool {
private workers: Worker[] = [];
private idle: Worker[] = [];
private queue: { task: any; resolve: (v: any) => void }[] = [];
constructor(size: number) {
for (let i = 0; i < size; i++) {
const w = new Worker(...);
w.onmessage = (e) => {
// 결과 전달 + idle 로
const { resolve } = this.activeMap.get(w)!;
resolve(e.data);
this.idle.push(w);
this.processQueue();
};
this.workers.push(w);
this.idle.push(w);
}
}
run(task: any): Promise<any> {
return new Promise((resolve) => {
this.queue.push({ task, resolve });
this.processQueue();
});
}
// ...
}
또는 piscina-browser 같은 라이브러리.
TypeScript 설정
// tsconfig.worker.json
{
"compilerOptions": {
"lib": ["ES2022", "WebWorker"],
"module": "ESNext"
}
}
Web Worker 한계
- DOM access X.
- 일부 API X (storage 일부, alert).
- main 보다 setup 비용.
🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| < 50ms | main thread |
| 50-500ms | Worker (Comlink) |
| > 500ms | Worker pool |
| 큰 array | SharedArrayBuffer (가능) |
| Canvas 그림 | OffscreenCanvas worker |
| ML inference | Worker + WASM / WebGPU |
| Cross-tab 공유 state | Shared Worker + Broadcast Channel |
❌ 안티패턴
- Worker 매번 새로: setup 비싸. Pool / 재사용.
- postMessage 큰 객체 deep clone: 느림. transferable.
- DOM access in worker: 안 됨.
- Worker 안 import 큰 dep: bundle 큰. tree-shake.
- Comlink 매 호출 생성: 한 번 wrap.
- SharedArrayBuffer 가정 + COOP/COEP 없음: undefined.
- Termination 안 함: leak.
🤖 LLM 활용 힌트
- Comlink 가 RPC + type 안전.
- Vite/Webpack worker import 자연.
- 큰 data = transferable, 양방향 = SAB.