--- id: web-offmain-webworker title: Web Worker / Comlink — main thread 비우기 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [web, worker, performance, vibe-coding] tech_stack: { language: "TS", applicable_to: ["Frontend"] } applied_in: [] aliases: [Web Worker, Service Worker, SharedArrayBuffer, Comlink, transferable, OffscreenCanvas] --- # 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 ```ts // 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) ```ts // worker.ts import * as Comlink from 'comlink'; class HeavyAPI { async process(items: number[]): Promise { return items.reduce((s, x) => s + x * x, 0); } async fft(samples: Float32Array): Promise { ... } } Comlink.expose(new HeavyAPI()); ``` ```ts // 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(worker); const sum = await api.process([1, 2, 3]); ``` → promise 기반 RPC, type-safe. ### Vite worker import ```ts import HeavyWorker from './worker?worker'; const worker = new HeavyWorker(); ``` ### Transferable (zero-copy) ```ts const buf = new ArrayBuffer(10_000_000); worker.postMessage({ buf }, [buf]); // buf 는 main 에서 더 사용 못 함 — transferred ``` → 큰 데이터 빠르게. ### SharedArrayBuffer (양방향 공유) ```ts // 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 ```ts // main const canvas = document.getElementById('c') as HTMLCanvasElement; const offscreen = canvas.transferControlToOffscreen(); worker.postMessage({ canvas: offscreen }, [offscreen]); ``` ```ts // 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 통합 ```tsx function useWorker(input: T): R | null { const [result, setResult] = useState(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: ```tsx const apiRef = useRef>(); useEffect(() => { const w = new Worker(...); apiRef.current = Comlink.wrap(w); return () => w.terminate(); }, []); ``` ### Pool (여러 worker 병렬) ```ts 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 { return new Promise((resolve) => { this.queue.push({ task, resolve }); this.processQueue(); }); } // ... } ``` 또는 piscina-browser 같은 라이브러리. ### TypeScript 설정 ```jsonc // 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. ## 🔗 관련 문서 - [[Web_Performance_Core_Vitals]] - [[Node_Worker_ChildProcess]] - [[React_Animation_Performance]]