SharedArrayBuffer ใน JavaScript
บทความนี้อธิบายเกี่ยวกับ SharedArrayBuffer
ใน JavaScript
เราจะให้คำอธิบายในเชิงลึกเกี่ยวกับพื้นฐานของ SharedArrayBuffer
, วิธีการใช้งาน, กรณีการใช้งานเฉพาะ และข้อพิจารณาด้านความปลอดภัย
YouTube Video
SharedArrayBuffer ใน JavaScript
SharedArrayBuffer
เป็นเครื่องมือที่ทรงพลังใน JavaScript สำหรับการแชร์หน่วยความจำระหว่างหลายเธรด โดยเฉพาะเมื่อใช้ร่วมกับ Web Workers มันช่วยให้เกิดการประมวลผลแบบขนาน ทำให้มีประสิทธิภาพสำหรับงานที่ใช้การคำนวณหนักและแอปพลิเคชันที่ต้องการความสามารถแบบเรียลไทม์
SharedArrayBuffer คืออะไร?
SharedArrayBuffer
ให้บริการหน่วยความจำบัฟเฟอร์ใน JavaScript ที่ช่วยให้สามารถแชร์ข้อมูลไบนารีระหว่างหลายเธรด (ส่วนใหญ่สำหรับ Web Workers) โดยปกติ ArrayBuffer
จำเป็นต้องคัดลอกข้อมูลระหว่างเธรดหลักและ workers แต่ SharedArrayBuffer
ช่วยให้การแชร์หน่วยความจำเป็นไปโดยตรงโดยไม่ต้องคัดลอก ส่งผลให้ประสิทธิภาพดีขึ้นมาก
คุณสมบัติ
- หน่วยความจำที่ใช้ร่วมกัน ช่วยให้หลายเธรดสามารถทำงานกับพื้นที่หน่วยความจำเดียวกันได้
- การปรับปรุงประสิทธิภาพ เนื่องจากไม่ต้องก๊อปปี้ข้อมูล จึงช่วยลดค่าใช้จ่ายส่วนเกินเมื่อประมวลผลข้อมูลจำนวนมาก
- การซิงโครไนซ์ของเธรด
สามารถใช้ร่วมกับ
Atomics
เพื่อดำเนินการซิงโครไนซ์ ป้องกันความขัดแย้งเมื่อเข้าถึงหน่วยความจำ
ตัวอย่างการใช้งานพื้นฐาน
1// Create a 16-byte shared memory
2const sharedBuffer = new SharedArrayBuffer(16);
3
4// Treat it as an Int32Array
5const sharedArray = new Int32Array(sharedBuffer);
6
7// Set a value
8sharedArray[0] = 42;
9
10console.log(sharedArray[0]); // 42
ในตัวอย่างนี้ เราสร้างพื้นที่หน่วยความจำ 16 ไบต์โดยใช้ SharedArrayBuffer
และจัดการพื้นที่หน่วยความจำนั้นเป็น Int32Array
บัฟเฟอร์หน่วยความจำนี้สามารถแชร์ระหว่างหลายเธรดได้
การใช้งานร่วมกับ Web Workers
คุณค่าที่แท้จริงของ SharedArrayBuffer
จะเห็นได้ชัดเจนเมื่อใช้งานร่วมกับ Web Workers โค้ดด้านล่างนี้เป็นตัวอย่างการใช้หน่วยความจำที่แชร์ระหว่างเธรดหลักและ worker
บนเธรดหลัก
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Modify the memory
12// Output : Main thread: 100
13sharedArray[0] = 100;
14console.log("Main thread: ", sharedArray[0]);
ทางฝั่งของ Worker (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 // Use the received shared buffer
4 const sharedArray = new Int32Array(event.data);
5
6 // Read the contents of the memory
7 // Output : Worker thread: 100
8 console.log("Worker thread: ", sharedArray[0]);
9
10 // Change the value
11 sharedArray[0] = 200;
12};
- ในตัวอย่างนี้ เธรดหลักสร้างบัฟเฟอร์ที่แชร์และส่งต่อไปยัง worker worker สามารถเข้าถึงบัฟเฟอร์นี้เพื่ออ่านและแก้ไขค่าได้ ด้วยวิธีนี้ ข้อมูลสามารถแชร์ระหว่างเธรดได้โดยไม่ต้องคัดลอก
การตรวจสอบการอัปเดตแบบสองทิศทาง
โดยการใช้ SharedArrayBuffer
ทั้งเธรดหลักและ worker สามารถอ่านและเขียนข้อมูลในหน่วยความจำเดียวกัน ทำให้สามารถตรวจสอบการอัปเดตแบบสองทิศทางได้ ด้านล่างนี้เป็นตัวอย่างที่เธรดหลักตั้งค่าค่า ตัว worker เปลี่ยนค่านี้ และจากนั้นเธรดหลักจะตรวจสอบการอัปเดตอีกครั้ง
บนเธรดหลัก
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Set initial value
12// Output : Main thread initial: 100
13sharedArray[0] = 100;
14console.log("Main thread initial:", sharedArray[0]);
15
16// Listen for worker confirmation
17worker.onmessage = () => {
18 // Output : Main thread after worker update: 200
19 console.log("Main thread after worker update:", sharedArray[0]);
20};
ทางฝั่งของ Worker (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 const sharedArray = new Int32Array(event.data);
4
5 // Read initial value
6 // Output : Worker thread received: 100
7 console.log("Worker thread received:", sharedArray[0]);
8
9 // Update the value
10 sharedArray[0] = 200;
11
12 // Notify main thread
13 self.postMessage("Value updated");
14};
- ในตัวอย่างนี้ เธรดหลักจะเขียนค่า
100
ก่อน แล้ว worker จะอ่านค่าและเปลี่ยนเป็น200
หลังจากนั้น worker จะแจ้งให้เธรดหลักทราบ และเธรดหลักจะอ่านหน่วยความจำที่ใช้ร่วมกันอีกครั้งเพื่อยืนยันการอัปเดต ด้วยวิธีนี้ การรวมการแจ้งเตือนทำให้สามารถตรวจสอบการอัปเดตแบบสองทิศทางได้
การซิงโครไนซ์ด้วย Atomics
เมื่อใช้หน่วยความจำที่แชร์ จำเป็นต้องระมัดระวังเกี่ยวกับเงื่อนไขการแย่งชิงข้อมูลและความไม่สอดคล้องกันของข้อมูล เมื่อหลายเธรดเข้าถึงหน่วยความจำเดียวกันพร้อมกัน อาจเกิดความขัดแย้งได้ เพื่อป้องกันสิ่งนี้ JavaScript ใช้วัตถุ Atomics
เพื่อซิงโครไนซ์
ตัวอย่างเช่น เพื่อเพิ่มค่าของเคาน์เตอร์อย่างปลอดภัยด้วยเธรดหลายเธรด คุณสามารถใช้ Atomics
เพื่อป้องกันความขัดแย้งได้
1const sharedBuffer = new SharedArrayBuffer(16);
2const sharedArray = new Int32Array(sharedBuffer);
3
4// Increment the counter
5Atomics.add(sharedArray, 0, 1);
6
7console.log(Atomics.load(sharedArray, 0)); // 1
Atomics.add
จะเพิ่มค่าที่ตำแหน่งที่ระบุเชิงอะตอมและคืนค่าใหม่ การดำเนินการนี้รับประกันได้ว่าจะปลอดจากความขัดแย้งกับเธรดอื่นๆ Atomics.load
ยังใช้เพื่ออ่านค่าจากหน่วยความจำที่แชร์อย่างปลอดภัย
การรอและการแจ้งเตือนโดยใช้ Atomics.wait
และ Atomics.notify
เมื่อใช้ SharedArrayBuffer
อาจมีกรณีที่ worker ต้องรอจนกว่าจะมีเงื่อนไขบางอย่างเกิดขึ้น และเมื่อ worker ตัวอื่นทำเงื่อนไขนั้นสำเร็จ จะต้องแจ้งเตือน worker ที่รออยู่ ในกรณีแบบนี้ Atomics.wait
และ Atomics.notify
จะมีประโยชน์
Atomics.wait
จะบล็อกเธรดไว้จนกว่าค่าที่ตำแหน่งหนึ่งในหน่วยความจำที่ใช้ร่วมกันจะเปลี่ยนแปลง ส่วน Atomics.notify
จะเป็นตัวแจ้งให้เธรดที่กำลังรออยู่ดำเนินการต่อไป วิธีนี้ช่วยให้สามารถรอและแจ้งเตือนกันระหว่าง worker หลายตัวได้อย่างปลอดภัย อย่างไรก็ตาม Atomics.wait
ไม่สามารถใช้ได้บนเธรดหลักและ สามารถใช้ได้เฉพาะใน worker เท่านั้น
1// Create a shared buffer (1 Int32 slot is enough for signaling)
2const sharedBuffer = new SharedArrayBuffer(4);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create workers with names
6const waiter = new Worker('worker.js', { name: 'waiter' });
7const notifier = new Worker('worker.js', { name: 'notifier' });
8
9// Pass the shared buffer to both
10waiter.postMessage(sharedBuffer);
11notifier.postMessage(sharedBuffer);
12
13// Listen for messages
14waiter.onmessage = (event) => {
15 console.log(`[Main] Message from waiter:`, event.data);
16};
17notifier.onmessage = (event) => {
18 console.log(`[Main] Message from notifier:`, event.data);
19};
- บนเธรดหลัก จะสร้าง
SharedArrayBuffer
เพื่อใช้เป็นหน่วยความจำที่ใช้ร่วมกัน และแปลงเป็นInt32Array
ที่มีเพียงหนึ่งสมาชิก ช่องข้อมูลจำนวนเต็มช่องเดียวนี้ถูกใช้เป็นสัญญาณเพื่อให้การทำงานประสานกันระหว่าง worker จากนั้นจะสร้าง worker สองตัว และกำหนดบทบาทให้แต่ละตัวโดยใช้คุณสมบัติname
ได้แก่waiter
(ผู้รอ) และnotifier
(ผู้แจ้งเตือน) สุดท้าย buffer ที่ใช้งานร่วมกันจะถูกส่งไปยัง worker ทั้งสอง และมีการตั้งค่าตัวจัดการonmessage
เพื่อรับข้อความที่ส่งจากแต่ละ worker
ทางฝั่งของ Worker (worker.js
)
1// worker.js
2onmessage = (event) => {
3 const sharedArray = new Int32Array(event.data);
4
5 if (self.name === 'waiter') {
6 postMessage('Waiter is waiting...');
7 // Wait until notifier signals index 0
8 Atomics.wait(sharedArray, 0, 0);
9 postMessage('Waiter was notified!');
10 }
11
12 if (self.name === 'notifier') {
13 postMessage('Notifier is preparing...');
14 setTimeout(() => {
15 // Notify waiter after 2 seconds
16 Atomics.store(sharedArray, 0, 1);
17 Atomics.notify(sharedArray, 0, 1);
18 postMessage('Notifier has sent the signal!');
19 }, 2000);
20 }
21};
22// Output
23// [Main] Message from waiter: Waiter is waiting...
24// [Main] Message from notifier: Notifier is preparing...
25// [Main] Message from notifier: Notifier has sent the signal!
26// [Main] Message from waiter: Waiter was notified!
- ในตัวอย่างนี้ worker
waiter
จะอยู่ในสถานะรอโดยใช้Atomics.wait
ตราบเท่าที่ค่าที่ตำแหน่งที่อยู่ 0 เป็น 0 ในทางกลับกัน เมื่อ workernotifier
เปลี่ยนค่าเป็น 123 ด้วยAtomics.store
และเรียกใช้Atomics.notify
workerwaiter
จะกลับมาทำงานต่อและสามารถรับค่าที่ถูกอัปเดตได้ ด้วยวิธีนี้ สามารถรอและแจ้งเตือนระหว่างเธรดอย่างมีประสิทธิภาพและปลอดภัย
กรณีการใช้งานของ SharedArrayBuffer
SharedArrayBuffer
มีประโยชน์อย่างยิ่งสำหรับกรณีการใช้งานต่อไปนี้:
- การประมวลผลแบบเรียลไทม์ เหมาะสำหรับแอปพลิเคชันที่ต้องการความหน่วงต่ำ เช่น การประมวลผลเสียง วิดีโอ หรือเอนจิ้นเกม ซึ่งต้องใช้การแชร์ข้อมูลทันทีระหว่างเธรด
- การประมวลผลแบบขนาน
เมื่อประมวลผลข้อมูลจำนวนมากด้วยหลายเธรดพร้อมกัน การใช้
SharedArrayBuffer
จะช่วยหลีกเลี่ยงการก๊อปปี้หน่วยความจำและเพิ่มประสิทธิภาพได้ - การเรียนรู้ของเครื่อง ด้วยการประมวลผลงาน เช่น การเตรียมข้อมูลและการอนุมานแบบขนาน ทำให้สามารถคำนวณได้อย่างมีประสิทธิภาพ
ข้อพิจารณาด้านความปลอดภัย
SharedArrayBuffer
เป็นคุณสมบัติที่ทรงพลัง แต่ก็มีความเสี่ยงด้านความปลอดภัยเช่นกัน โดยเฉพาะอย่างยิ่ง ความกังวลเกี่ยวกับการโจมตีผ่าน channel ด้านข้าง เช่น Spectre
ได้หยุดสนับสนุนไว้ชั่วคราว เพื่อบรรเทาช่องโหว่นี้ เบราว์เซอร์ได้ดำเนินมาตรการต่อไปนี้:
- การแยกไซต์ (Site Isolation)
เว็บไซต์ที่อนุญาตให้ใช้
SharedArrayBuffer
จะทำงานในโปรเซสที่แยกออกจากเว็บไซต์อื่นโดยสมบูรณ์ - นโยบายทรัพยากรข้ามโดเมน
เพื่อให้สามารถใช้
SharedArrayBuffer
ได้ ต้องกำหนด headerCross-Origin-Opener-Policy
และCross-Origin-Embedder-Policy
ให้ถูกต้อง
ตัวอย่างเช่น โดยการตั้งค่า head ดังต่อไปนี้ การใช้ SharedArrayBuffer
จะกลายเป็นไปได้:
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
สิ่งนี้ช่วยป้องกันไม่ให้ทรัพยากรภายนอกรบกวนเนื้อหาปัจจุบันและเพิ่มความปลอดภัย
สรุป
SharedArrayBuffer
เป็นเครื่องมือที่ทรงพลังมากสำหรับการแชร์หน่วยความจำระหว่างเธรดหลายตัว เป็นเทคโนโลยีที่สำคัญสำหรับการปรับปรุงประสิทธิภาพ และผลลัพธ์มีความเด่นชัดเป็นพิเศษในด้านการประมวลผลแบบเรียลไทม์และการคำนวณแบบขนาน อย่างไรก็ตาม มันยังมีความเสี่ยงด้านความปลอดภัย ดังนั้นการตั้งค่าและการทำให้การทำงานซิงโครไนซ์อย่างถูกต้องจึงเป็นสิ่งสำคัญ
ด้วยการใช้ SharedArrayBuffer
คุณสามารถสร้างเว็บแอปพลิเคชันที่ล้ำสมัยและมีประสิทธิภาพสูงกว่าเดิม
คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย