[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
---
|
||||
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<number> {
|
||||
return items.reduce((s, x) => s + x * x, 0);
|
||||
}
|
||||
async fft(samples: Float32Array): Promise<Float32Array> { ... }
|
||||
}
|
||||
|
||||
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<HeavyAPI>(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<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:
|
||||
```tsx
|
||||
const apiRef = useRef<Remote<HeavyAPI>>();
|
||||
|
||||
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<any> {
|
||||
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]]
|
||||
Reference in New Issue
Block a user