SharedArrayBuffer في JavaScript
تشرح هذه المقالة SharedArrayBuffer
في JavaScript۔
سنقدم شرحًا مفصلًا لأساسيات SharedArrayBuffer
، وكيفية استخدامه، والحالات العملية المحددة، واعتبارات الأمان.۔
YouTube Video
SharedArrayBuffer في JavaScript
SharedArrayBuffer
هي أداة قوية في JavaScript لمشاركة الذاكرة بين عدة خيوط (Threads)۔ خاصة عند استخدامها مع Web Workers، توفر معالجة متوازية، مما يجعلها فعالة للمهام المرهقة حسابيًا والتطبيقات التي تتطلب قدرات في الوقت الفعلي۔
ما هو SharedArrayBuffer؟
SharedArrayBuffer
يوفر ذاكرة تخزين مؤقتة في JavaScript تسمح بمشاركة البيانات الثنائية بين عدة خيوط (خصوصًا Web Workers)۔ ArrayBuffer
العادي يتطلب نسخ البيانات بين الخيط الرئيسي والـ Workers، بينما يسمح SharedArrayBuffer
بمشاركة الذاكرة مباشرة دون الحاجة إلى النسخ، مما يحسن الأداء بشكل كبير۔
الميزات
- الذاكرة المشتركة يسمح للعديد من الخيوط (threads) بالعمل في نفس مساحة الذاكرة۔
- تحسين الأداء نظرًا لإمكانية الاستغناء عن النسخ، يتم تقليل العبء الإضافي عند معالجة كميات كبيرة من البيانات۔
- مزامنة الخيوط (threads)
يمكنك استخدامه مع
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
يمكن لكل من الخيط الرئيسي (main thread) والعاملين (workers) القراءة والكتابة إلى نفس الذاكرة، مما يتيح تأكيد التحديث ثنائي الاتجاه۔ فيما يلي مثال حيث يقوم الخيط الرئيسي بتعيين قيمة، ثم يقوم عامل بتغيير هذه القيمة، وبعد ذلك يتحقق الخيط الرئيسي من التحديث۔
في الخيط الرئيسي
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
أولاً، وبعد أن يقرأها العامل، يكتبها مرة أخرى كـ200
۔ بعد ذلك، يقوم العامل بإخطار الخيط الرئيسي، ثم يقرأ الخيط الرئيسي الذاكرة المشتركة مرة أخرى لتأكيد التحديث۔ بهذه الطريقة، يتيح الجمع بين الإشعارات تأكيد التحديث ثنائي الاتجاه۔
المزامنة باستخدام 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
هناك حالات يحتاج فيها عامل إلى الانتظار حتى يتحقق شرط معين، وعندما يفي عامل آخر بهذا الشرط، يجب عليه إخطار العامل المنتظر۔ في مثل هذه الحالات، تكون Atomics.wait
وAtomics.notify
مفيدة۔
Atomics.wait
تقوم بحجب الخيط حتى تتغير القيمة عند مؤشر معين في الذاكرة المشتركة، بينما Atomics.notify
تقوم بإخطار الخيوط المنتظرة بأنه يمكنها المتابعة۔ يتيح هذا الانتظار والإخطار بشكل آمن بين عدة عمال۔ ومع ذلك، لا يمكن استخدام Atomics.wait
في الخيط الرئيسي وهو متوفر فقط داخل العمال (workers)۔
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
يحتوي على عنصر واحد فقط۔ يُستخدم هذا الموقع الصحيح المفرد كإشارة للمزامنة بين العمال۔ بعد ذلك، يتم إنشاء عاملين ويتم تعيين دور لكل منهما باستخدام خاصيةname
: الأول هوwaiter
(دور الانتظار)، والثاني هوnotifier
(دور الإشعار)۔ وأخيرًا، يتم تمرير المخزن المؤقت المشترك إلى كلا العاملين ويتم إعداد معالجاتonmessage
حتى يمكن استقبال الرسائل المرسلة من كل عامل۔
على جانب 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!
- في هذا المثال، يبقى عامل
waiter
في حالة انتظار باستخدامAtomics.wait
طالما أن القيمة عند الفهرس0
هي0
۔ من ناحية أخرى، عندما يقوم عاملnotifier
بتغيير القيمة إلى123
باستخدامAtomics.store
ويناديAtomics.notify
، سيستأنف عاملwaiter
العمل ويتمكن من الحصول على القيمة المحدثة۔ وبذلك يمكن تحقيق انتظار وإشعار فعال وآمن بين الخيوط۔
سيناريوهات استخدام SharedArrayBuffer
SharedArrayBuffer
مفيد بشكل خاص في الحالات التالية:۔
- المعالجة في الوقت الفعلي إنه مناسب للتطبيقات التي تتطلب انخفاض التأخير (زمن الاستجابة)، مثل معالجة الصوت والفيديو أو محركات الألعاب، حيث يجب مشاركة البيانات فورياً بين الخيوط۔
- الحوسبة المتوازية
عند معالجة كميات كبيرة من البيانات في الوقت نفسه باستخدام عدة خيوط، يمنع استخدام
SharedArrayBuffer
نسخ الذاكرة ويمكن أن يحسّن الأداء۔ - التعلم الآلي من خلال موازاة المهام مثل المعالجة المسبقة للبيانات والاستدلال، يصبح الحوسبة الفعالة ممكنة۔
اعتبارات الأمان
SharedArrayBuffer
ميزة قوية، لكنها تحمل أيضًا مخاطر أمنية۔ على وجه الخصوص، فإن المخاوف بشأن هجمات القنوات الجانبية مثل Spectre
قد أوقفت دعمها مؤقتًا۔ لتقليل هذه الثغرة الأمنية، قامت المتصفحات بتنفيذ التدابير التالية:۔
- عزل الموقع
المواقع التي تسمح باستخدام
SharedArrayBuffer
ستعمل في عملية (process) معزولة تمامًا عن المواقع الأخرى۔ - سياسة موارد المصدر المشترك (Cross-Origin Resource Policy)
لاستخدام
SharedArrayBuffer
يجب ضبط رؤوسCross-Origin-Opener-Policy
وCross-Origin-Embedder-Policy
بشكل صحيح۔
على سبيل المثال، من خلال إعداد رؤوس مثل التالية، يصبح استخدام SharedArrayBuffer
ممكنًا:۔
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
هذا يمنع الموارد الخارجية من التدخل في المحتوى الحالي ويزيد من الأمان۔
الملخص
SharedArrayBuffer
هي أداة قوية جدًا لمشاركة الذاكرة بين عدة خيوط برمجية۔ إنها تقنية أساسية لتحسين الأداء، وتظهر تأثيراتها بشكل خاص في مجالي المعالجة في الوقت الحقيقي والحوسبة المتوازية۔ ومع ذلك، فإنه ينطوي أيضًا على مخاطر أمنية، لذا فإن التهيئة الصحيحة والمزامنة مهمة۔
من خلال الاستفادة من SharedArrayBuffer
يمكنك بناء تطبيقات ويب أكثر تطورًا وذات أداء أعلى۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔