"매 main thread ↔ Worker 통신 의 latency 의 4가지 의 source: serialization, structured clone, queue, dispatch — 매 Transferable / SharedArrayBuffer / batching / Comlink RPC 의 mitigation". 매 postMessage 의 default behavior 의 매 bytes 의 deep clone — 매 large payload 의 매 ms 의 단위. 매 2026 의 modern approach 의 OffscreenCanvas, transferable streams, Atomics.
매 핵심
매 Latency Source
Structured clone — 매 object 의 deep copy, 매 size 의 비례.
Serialization — 매 V8 의 internal format 의 conversion.
Event queue — 매 receiver 의 task queue 의 enqueue.
Dispatch overhead — 매 microtask boundary, 매 ~0.1-0.5ms.
매 Mitigation 전략
Transferable Objects — ArrayBuffer, MessagePort, OffscreenCanvas, ReadableStream 의 zero-copy 의 ownership transfer.
SharedArrayBuffer + Atomics — 매 shared memory, 매 lock-free 통신.
Batching — 매 multiple event 의 single message 의 합침.
MessageChannel — 매 dedicated channel, 매 main thread 의 bypass.
// main.ts
constbuffer=newArrayBuffer(10_000_000);// 10MB
constview=newFloat32Array(buffer);view.fill(1.0);worker.postMessage({buffer},[buffer]);// 매 두번째 인자 의 transfer list
console.log(buffer.byteLength);// 0 — 매 neutered
// worker.ts
self.onmessage=(e)=>{constbuffer: ArrayBuffer=e.data.buffer;// 매 zero-copy 의 ownership 의 받음
constview=newFloat32Array(buffer);// ... process
};
// 매 COOP/COEP header 의 필요 — Cross-Origin-Opener-Policy: same-origin
constsab=newSharedArrayBuffer(1024);constview=newInt32Array(sab);worker.postMessage({sab});// worker 의 spinlock 의 wait
Atomics.wait(view,0,0);// 매 view[0]==0 의 동안 sleep
constdata=view[1];// main 의 signal
view[1]=42;view[0]=1;Atomics.notify(view,0,1);// 매 worker 의 wake
Pattern 4: Batching (event coalescing)
// main.ts
constqueue: Event[]=[];letscheduled=false;functionforward(event: Event){queue.push(event);if(!scheduled){scheduled=true;queueMicrotask(()=>{worker.postMessage({events: queue.splice(0)});scheduled=false;});}}window.addEventListener('mousemove',forward);// 매 60fps 의 single batch
Pattern 5: OffscreenCanvas (rendering off main thread)
// main.ts
constcanvas=document.querySelector('canvas')!;constoffscreen=canvas.transferControlToOffscreen();worker.postMessage({canvas: offscreen},[offscreen]);// worker.ts
self.onmessage=(e)=>{constcanvas=e.data.canvasasOffscreenCanvas;constctx=canvas.getContext('2d')!;// 매 main thread 의 block X 의 render
functionframe() {ctx.clearRect(0,0,canvas.width,canvas.height);// ... draw
requestAnimationFrame(frame);}frame();};
Pattern 6: MessageChannel (direct port)
constchannel=newMessageChannel();worker1.postMessage({port: channel.port1},[channel.port1]);worker2.postMessage({port: channel.port2},[channel.port2]);// 매 worker 끼리 매 main thread 의 bypass 의 직접 통신
Pattern 7: Transferable Stream (2026)
// main.ts
conststream=newReadableStream({start(ctrl){fetch('/large').then(asyncr=>{constreader=r.body!.getReader();while(true){const{value,done}=awaitreader.read();if(done)break;ctrl.enqueue(value);}ctrl.close();});}});worker.postMessage({stream},[stream]);// 매 zero-copy
// worker.ts
self.onmessage=async(e)=>{forawait(constchunkofe.data.stream){process(chunk);}};
매 결정 기준
상황
기법
Large binary payload
Transferable ArrayBuffer
RPC ergonomics
Comlink
Lock-free shared state
SharedArrayBuffer + Atomics
High-frequency events
Batching with queueMicrotask
Canvas rendering
OffscreenCanvas
Worker-to-worker
MessageChannel
Streaming data
Transferable streams
기본값: Transferable ArrayBuffer + Comlink RPC + 매 60fps event 의 batching.
언제: postMessage 의 large payload 의 transferable 의 변환 권고, COOP/COEP header 의 troubleshoot, batching 의 적절한 frequency 의 제안.
언제 X: 매 native thread (C++ worker) 의 통신 — 매 다른 domain.
❌ 안티패턴
Large object 의 매 postMessage: 매 deep clone, 매 GC pressure, 매 main thread 의 stall.
Per-event 매 postMessage (mousemove): 매 queue 의 폭주, 매 jank.
Atomics 의 SharedArrayBuffer 없음: 매 cross-origin isolation 의 필요.
Worker 의 dynamic import 의 누락: 매 type: 'module' 의 누락.
Transferable 의 후 의 access: 매 neutered, 매 byteLength=0.