postMessage และ Transferables
วิธีที่ postMessage คัดลอกข้อมูล — structured clone
หัวข้อที่มีชื่อว่า “วิธีที่ postMessage คัดลอกข้อมูล — structured clone”โดยค่าเริ่มต้น 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
Transferables — โอนกรรมสิทธิ์แบบ zero-copy
หัวข้อที่มีชื่อว่า “Transferables — โอนกรรมสิทธิ์แบบ zero-copy”เพื่อหลีกเลี่ยงการคัดลอก buffer ขนาดใหญ่ ให้ส่งเป็น transferables ในอาร์กิวเมนต์ที่สองของ postMessage กรรมสิทธิ์ของ buffer จะโอนไปยังผู้รับ — ฝั่งต้นทางจะ detached (ความยาวเป็นศูนย์ ใช้งานไม่ได้) ทันที ไม่มีการคัดลอก byte ใดๆ มีแค่ pointer ที่ย้ายระหว่างเธรด
const buffer = new ArrayBuffer(1024 * 1024); // 1 MBworker.postMessage(buffer, [buffer]); // โอนกรรมสิทธิ์console.log(buffer.byteLength); // 0 — detached แล้ว!อาร์กิวเมนต์ที่สองคือ array ของ transferable objects คุณสามารถผสมข้อมูลปกติกับ transferable ได้:
worker.postMessage({ kind: 'frame', data: imageBuffer }, [imageBuffer]);เมื่อใดควรใช้ transferables
หัวข้อที่มีชื่อว่า “เมื่อใดควรใช้ transferables”ArrayBufferขนาดใหญ่ (pixel ของภาพ, ตัวอย่างเสียง, เนื้อหาไฟล์ binary) — transferables เร็วกว่า clone หลายเท่าสำหรับข้อมูลที่มีขนาดมากกว่า ~1 MBMessagePort— โอน port ให้ worker สองตัวคุยกันโดยตรงโดยไม่ผ่าน main threadOffscreenCanvas— โอน canvas rendering context เข้าไปใน worker เพื่อวาดภาพโดยไม่บล็อก UI thread- สำหรับ plain objects และ strings ขนาดเล็ก structured clone ก็ใช้ได้ดีและง่ายกว่า
รันได้: โอน ArrayBuffer และสังเกต detachment
หัวข้อที่มีชื่อว่า “รันได้: โอน ArrayBuffer และสังเกต detachment”demo นี้สร้าง buffer ขนาด 1 MB เติม byte แรกด้วยค่า 42 แล้วโอนให้ worker สังเกต byteLength ก่อนและหลังการ transfer
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| 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 rateSquoosh (เครื่องมือบีบอัดภาพของ Google) — ส่ง pixel data เป็น transferable
ArrayBufferเข้า-ออก codec worker แทนการ clone ทำให้บีบอัดภาพขนาดใหญ่ได้โดยไม่ค้าง UI