الـ `SharedArrayBuffer` في TypeScript
تشرح هذه المقالة الـ SharedArrayBuffer في TypeScript۔
سنشرح SharedArrayBuffer في TypeScript مع أمثلة عملية۔
YouTube Video
الـ SharedArrayBuffer في TypeScript
SharedArrayBuffer هو آلية لمشاركة نفس مساحة الذاكرة بين عدة خيوط تنفيذ، مثل Web Workers۔ من خلال دمجه مع Atomics، يمكنك إدارة التعارضات على البيانات وتنفيذ عمليات مشاركة الذاكرة بزمن استجابة منخفض۔
المتطلبات والملاحظات
عند استخدام SharedArrayBuffer في المتصفح، يلزم وجود رؤوس COOP وCOEP لتلبية متطلبات الأمان المعروفة باسم العزل عبر مصادر مختلفة (cross-origin isolation)۔ في Node.js، يمكن التعامل مع الذاكرة المشتركة بسهولة نسبية باستخدام worker_threads۔
المفاهيم الأساسية
SharedArrayBuffer هو كائن يمثل تسلسلاً من البايتات بطول ثابت ويمكنك قراءة وكتابة الأرقام عبر TypedArray وعروض مشابهة لها۔ عمليات القراءة/الكتابة البسيطة غير متزامنة، لذلك تستخدم واجهة Atomics لضمان العمليات الذرية وتستخدم آليتي wait و notify للتنسيق۔
عداد بسيط (إصدار المتصفح)
في هذا المثال، يقوم الخيط الرئيسي بإنشاء SharedArrayBuffer ثم تمريره إلى Web Worker، بحيث يقوم كل منهما بزيادة عداد مشترك۔ هذا المثال يوضح النمط الأدنى: الجمع الذري باستخدام Atomics.add والقراءة باستخدام Atomics.load۔
main.ts (جانب المتصفح)
يوضح هذا المثال كيف يقوم الخيط الرئيسي بإنشاء SharedArrayBuffer ثم يشاركه مع عامل (Worker) للوصول متعدد الخيوط۔
1// main.ts
2// Create a 4-byte (Int32) shared buffer for one counter
3const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1);
4const counter = new Int32Array(sab); // view over the buffer
5
6// Create worker and transfer the SharedArrayBuffer
7const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
8worker.postMessage({ sab });
9
10// Increase counter in main thread every 200ms
11setInterval(() => {
12 const newVal = Atomics.add(counter, 0, 1) + 1; // Atomically increment
13 console.log('Main incremented ->', newVal);
14}, 200);
15
16// Listen for messages (optional)
17worker.onmessage = (e) => {
18 console.log('From worker:', e.data);
19};- في هذا الكود، يستخدم الخيط الرئيسي
Atomics.addلزيادة القيمة بشكل ذري۔ ومن جانبworker.ts، يمكن الوصول إلى نفسSharedArrayBufferوالتعامل معه۔
worker.ts (عامل المتصفح)
هذا مثال يتلقى فيه العامل (Worker) نفس المخبأ المشترك (shared buffer) ويقوم بتخفيض قيمته أو التلاعب بها بشكل دوري۔
1// worker.ts
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const counter = new Int32Array(sab);
5
6 // Every 350ms, atomically add 2 (demonstration)
7 setInterval(() => {
8 const newVal = Atomics.add(counter, 0, 2) + 2;
9 // Optionally notify main thread (postMessage)
10 self.postMessage(`Worker added 2 -> ${newVal}`);
11 }, 350);
12};- العامل (Worker) يتعامل مع نفس الذاكرة عن طريق
Int32Arrayوتتم التحديثات بدون وجود تعارضات بفضلAtomics۔
المزامنة باستخدام wait/notify
من خلال استخدام Atomics.wait وAtomics.notify يمكنك تعليق الخيوط حتى تتحقق شروط معينة، مما يتيح المزامنة بناءً على الأحداث داخل خيوط العمل (workers)۔ في المتصفحات، أنسب طريقة هي استخدام Atomics.wait داخل عامل (Worker)۔
producer.ts (عامل إنتاج في المتصفح)
المنتِج يكتب البيانات ويشعر المستهلك باستخدام notify۔
1// producer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab); // state[0] will be 0=empty,1=filled
5
6 // produce every 500ms
7 setInterval(() => {
8 // produce: set state to 1 (filled)
9 Atomics.store(state, 0, 1);
10 Atomics.notify(state, 0, 1); // wake one waiter
11 self.postMessage('produced');
12 }, 500);
13};consumer.ts (عامل مستهلك في المتصفح)
المستهلك ينتظر باستخدام Atomics.wait ثم يستأنف المعالجة عند استلام إشعار من المنتِج۔
1// consumer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab);
5
6 // consumer loop
7 (async function consumeLoop() {
8 while (true) {
9 // if state is 0 => wait until notified (value changes)
10 while (Atomics.load(state, 0) === 0) {
11 // Blocks this worker until notified or timeout
12 Atomics.wait(state, 0, 0);
13 }
14 // consume (reset to 0)
15 // (processing simulated)
16 // read data from another shared buffer in real scenarios
17 Atomics.store(state, 0, 0);
18 // continue loop
19 self.postMessage('consumed');
20 }
21 })();
22};- في هذا النمط، يقوم المنتِج بإشعار المستهلك عبر
Atomics.notifyوينتظر المستهلك بكفاءة عبرAtomics.wait۔ لا يمكن استدعاءAtomics.waitفي الخيط الرئيسي في المتصفحات۔ لمنع تجمد واجهة المستخدم، يقتصر استخدامAtomics.waitفقط داخل العمال (Workers)۔
مثال عملي باستخدام Node.js (worker_threads)
في بيئة Node.js، يمكنك إدارة وظيفة SharedArrayBuffer عبر worker_threads۔ فيما يلي مثال مكتوب بـ TypeScript لبيئة Node.js۔
main-node.ts
الخيط الرئيسي ينشئ المخبأ ويمرره إلى العامل (Worker)۔
1// main-node.ts
2import { Worker } from 'worker_threads';
3import path from 'path';
4
5// Create SharedArrayBuffer for one 32-bit integer
6const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
7const counter = new Int32Array(sab);
8
9// Spawn worker and pass sab via workerData
10const worker = new Worker(path.resolve(__dirname, 'worker-node.js'), {
11 workerData: { sab }
12});
13
14// Log messages from worker
15worker.on('message', (msg) => console.log('Worker:', msg));
16
17// Increment in main thread periodically
18setInterval(() => {
19 const val = Atomics.add(counter, 0, 1) + 1;
20 console.log('Main increment ->', val);
21}, 300);worker-node.ts
يستخدم هذا المثال parentPort وworkerData من worker_threads في جانب العامل (Worker)۔
1// worker-node.ts
2import { parentPort, workerData } from 'worker_threads';
3
4const sab = workerData.sab as SharedArrayBuffer;
5const counter = new Int32Array(sab);
6
7// Periodically add 5
8setInterval(() => {
9 const val = Atomics.add(counter, 0, 5) + 5;
10 parentPort?.postMessage(`Added 5 -> ${val}`);
11}, 700);- في Node.js، لا توجد قيود COOP أو COEP كما في المتصفحات، لذا يمكن إدارة الذاكرة المشتركة بسهولة بمجرد استخدام
worker_threads۔ عند استخدام TypeScript، انتبه إلى ما إذا كنت تبني المشروع بإعدادات CommonJS أو ESM۔
نقاط تخص التايبينغ في TypeScript
SharedArrayBuffer وAtomics متضمَنتان في تعريفات أنواع DOM و المكتبات القياسية، لذا يمكنك استخدامهما مباشرة في TypeScript۔ عند تبادل الرسائل مع العمال (Workers)، من الأكثر أماناً تعريف واجهات وتحديد الأنواع بوضوح۔
1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };- تعريف الأنواع بشكل صريح يجعل التعامل مع
postMessageوonmessageأكثر أماناً ويسمح بالتحقق من الأنواع۔
حالات الاستخدام العملية
SharedArrayBuffer ليس ضروريًا دائمًا، لكنه فعال جدًا في الحالات التي تتطلب مزايا السرعة للذاكرة المشتركة۔ فهم الحالات التي يكون فيها فعالًا يؤدي إلى اختيار التكنولوجيا المناسبة۔
- مناسب للمعالجة ذات زمن الاستجابة المنخفض التي تتطلب مخابئ سريعة المشاركة ويمكن استخدامه لمعالجة الصوت/الفيديو في الزمن الحقيقي أو فيزيائيات الألعاب۔
- في عمليات تبادل البيانات البسيطة أو نقل كميات كبيرة من البيانات، قد يكون استخدام
Transferable ArrayBufferأوpostMessageأسهل من استخدامSharedArrayBuffer۔
القيود والأمان
لاستخدام SharedArrayBuffer في المتصفح، يلزم وجود عزلة عبر المصادر: حدِد COOP إلى same-origin-allow-popups وCOEP إلى require-corp۔ سيتم تعطيل SharedArrayBuffer ما لم يتم استيفاء هذه المتطلبات۔
نصائح الأداء وتصحيح الأخطاء
العمليات الذرية (Atomics) سريعة، ولكن الانتظار المتكرر والمزامنة المفرطة قد تؤدي إلى زيادة زمن الاستجابة۔
يجب التحقق من النقاط التالية لإدارة الذاكرة المشتركة بأمان وكفاءة۔
- يجب التعامل مع العروض مثل
Int32Arrayبمحاذاة صحيحة للبايتات۔ - كن واضحًا بشأن الفهارس التي يستخدمها كل عملية من نفس المخبأ المشترك، واحتفظ باتفاقيات ثابتة في كودك۔
- إذا تعاملت مع
SharedArrayBufferكـArrayBufferعادي، ستحدث تعارضات على البيانات۔ الكتابة إلى نفس الفهرس من عدة خيوط دون استخدامAtomicsأمر خطير۔
الملخص
باستخدام SharedArrayBuffer وAtomics بشكل صحيح، يمكنك تحقيق عمليات مشاركة ذاكرة آمنة وسريعة حتى مع TypeScript۔ البدء بأنماط تزامن بسيطة مثل العدادات أو الإشارات أسهل للفهم، ومن خلال إدارة التزامن الصحيح والفهارس بدقة يمكنك أن تكون فعالاً حتى في سيناريوهات زمن الاستجابة المنخفض۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔