ข้ามไปยังเนื้อหา

postMessage และ Transferables

โดยค่าเริ่มต้น postMessage จะ deep-clone ค่าโดยใช้ structured clone algorithm รองรับ plain objects, arrays, strings, TypedArray, Blob, Map, Set, Date และอื่นๆ อีกมาก วัตถุต้นฉบับฝั่งผู้ส่งจะไม่ถูกแตะต้องเลย ต้นทุนของการ clone นั้นแปรตามขนาดข้อมูล — clone object เล็กๆ แทบไม่มีผล แต่ clone ArrayBuffer ขนาด 100 MB จะใช้เวลาพอสมควรและอาจวัดได้สำหรับ workload แบบ real-time

const arr = [1, 2, 3];
worker.postMessage(arr); // arr ถูก clone — ต้นฉบับไม่เปลี่ยน
console.log(arr); // [1, 2, 3] — ยังอยู่ครบ

สิ่งที่ structured clone ไม่สามารถคัดลอกได้คือ: functions และ DOM nodes การพยายามส่งสิ่งเหล่านี้จะ throw DataCloneError

เพื่อหลีกเลี่ยงการคัดลอก buffer ขนาดใหญ่ ให้ส่งเป็น transferables ในอาร์กิวเมนต์ที่สองของ postMessage กรรมสิทธิ์ของ buffer จะโอนไปยังผู้รับ — ฝั่งต้นทางจะ detached (ความยาวเป็นศูนย์ ใช้งานไม่ได้) ทันที ไม่มีการคัดลอก byte ใดๆ มีแค่ pointer ที่ย้ายระหว่างเธรด

const buffer = new ArrayBuffer(1024 * 1024); // 1 MB
worker.postMessage(buffer, [buffer]); // โอนกรรมสิทธิ์
console.log(buffer.byteLength); // 0 — detached แล้ว!

อาร์กิวเมนต์ที่สองคือ array ของ transferable objects คุณสามารถผสมข้อมูลปกติกับ transferable ได้:

worker.postMessage({ kind: 'frame', data: imageBuffer }, [imageBuffer]);
  • ArrayBuffer ขนาดใหญ่ (pixel ของภาพ, ตัวอย่างเสียง, เนื้อหาไฟล์ binary) — transferables เร็วกว่า clone หลายเท่าสำหรับข้อมูลที่มีขนาดมากกว่า ~1 MB
  • MessagePort — โอน port ให้ worker สองตัวคุยกันโดยตรงโดยไม่ผ่าน main thread
  • OffscreenCanvas — โอน canvas rendering context เข้าไปใน worker เพื่อวาดภาพโดยไม่บล็อก UI thread
  • สำหรับ plain objects และ strings ขนาดเล็ก structured clone ก็ใช้ได้ดีและง่ายกว่า

demo นี้สร้าง buffer ขนาด 1 MB เติม byte แรกด้วยค่า 42 แล้วโอนให้ worker สังเกต byteLength ก่อนและหลังการ transfer

Browser Storage
ตัวเลือกBenefitCost
Structured clone (ค่าเริ่มต้น)ใช้งานง่าย ไม่ต้องจัดการ ownership เอง รองรับหลาย typeคัดลอกข้อมูลทั้งหมด ต้นทุนเวลาและ memory แปรตามขนาด buffer
Transferables (postMessage(data, [buffer]))Zero-copy โอน ownership แบบ pointer เร็วมากสำหรับ buffer ขนาดใหญ่ฝั่งต้นทาง detached ทันที ต้องออกแบบโค้ดไม่ให้แตะ buffer เดิมอีก
  • Clone ArrayBuffer ขนาดใหญ่โดยไม่จำเป็น — ถ้าไม่ต้องใช้ buffer ต้นฉบับต่อแล้ว ให้ transfer แทนด้วย postMessage(data, [buffer]) จะเร็วกว่าและประหยัด memory มากกว่าปล่อยให้ structured clone คัดลอกทั้งก้อน
  • พยายามอ่านหรือเขียน buffer หลัง transfer แล้ว — buffer จะ detached ทันที (byteLength เป็น 0) การเข้าถึง buffer นั้นอีกครั้งไม่ throw error แต่ได้ค่าว่างเปล่า ต้องเปลี่ยน reference ไปใช้ buffer ใหม่ที่ฝั่งรับสร้างขึ้นแทน
  • ลืมว่าอาร์กิวเมนต์ที่สองของ postMessage ต้องเป็น array — การส่ง buffer เดี่ยวๆ โดยไม่ห่อด้วย [] จะทำให้ transfer ไม่เกิดขึ้น และข้อมูลจะถูก clone ตามปกติแทน

💡 ตัวอย่างจากของจริง

Figma — ใช้ Web Worker ประมวลผลงานหนักอย่างการ render และ geometry calculation โดย transfer ArrayBuffer ของ document data ระหว่างเธรดเพื่อหลีกเลี่ยงต้นทุนการ clone ที่จะกระทบ frame rate

Squoosh (เครื่องมือบีบอัดภาพของ Google) — ส่ง pixel data เป็น transferable ArrayBuffer เข้า-ออก codec worker แทนการ clone ทำให้บีบอัดภาพขนาดใหญ่ได้โดยไม่ค้าง UI

postMessage ใช้ algorithm ใดในการคัดลอกข้อมูลโดยค่าเริ่มต้น?
หลังจากเรียก worker.postMessage(buffer, [buffer]) แล้ว buffer.byteLength ใน main thread เป็นเท่าไร?
ข้อใดต่อไปนี้ไม่ใช่ transferable type?