استخدام سلاسل WebAssembly من C وC++ وRust

تعرَّف على كيفية استخدام WebAssembly مع التطبيقات المتعدّدة المواضيع المكتوبة بلغات أخرى.

يُعدّ دعم سلاسل WebAssembly من أهم الإضافات التي تُحسِّن أداء WebAssembly. يتيح لك ذلك تشغيل أجزاء من الرمز البرمجي بشكل موازٍ على نوى منفصلة، أو تشغيل الرمز البرمجي نفسه على أجزاء مستقلة من بيانات الإدخال، وتوسيع نطاقه ليشمل أكبر عدد ممكن من النوى التي يمتلكها المستخدم، ما يؤدي إلى خفض وقت التنفيذ بشكلٍ كبير.

ستتعرّف في هذه المقالة على كيفية استخدام سلاسل مهام WebAssembly لتوفير تطبيقات متعددة سلاسل المهام مكتوبة بلغات مثل C وC++ وRust على الويب.

لا تشكّل سلاسل مهام WebAssembly ميزة منفصلة، بل هي عبارة عن مجموعة من المكوّنات التي تسمح لتطبيقات WebAssembly باستخدام النماذج التقليدية للترابط التسلسلي المتعدد على الويب.

عمال الويب

المكوّن الأول هو العاملون العاديون الذين تعرفهم وتحبّهم من JavaScript. تستخدِم سلاسل محادثات WebAssembly أداة الإنشاء new Worker لإنشاء سلاسل محادثات أساسية جديدة. تحمِّل كل سلسلة محادثات عنصرًا رابطًا لرمز JavaScript، ثم تستخدم السلسلة الرئيسية الأسلوب Worker#postMessage لمشاركة WebAssembly.Module المجمَّعة بالإضافة إلى WebAssembly.Memory المشترَكة (راجِع المعلومات أدناه) مع سلاسل المحادثات الأخرى. يؤدّي ذلك إلى إنشاء اتصال ويسمح لجميع سلاسل المهام هذه بتنفيذ رمز WebAssembly نفسه على الذاكرة المشتركة نفسها بدون استخدام JavaScript مرة أخرى.

إنّ Web Workers متوفّرة منذ أكثر من عقد من الزمن، وهي متوافقة على نطاق واسع ولا تتطلّب أي علامات خاصة.

SharedArrayBuffer

يتم تمثيل ذاكرة WebAssembly بكائن WebAssembly.Memory في JavaScript API. بشكلٍ تلقائي، يكون WebAssembly.Memory عبارة عن غلاف حول ArrayBuffer، وهو مخازن وحدات البايت الأوّلية التي لا يمكن الوصول إليها إلا من خلال سلسلة مهام واحدة.

> new WebAssembly.Memory({ initial:1, maximum:10 }).buffer
ArrayBuffer {  }

ولتتمكّن من استخدام ميزة "سلسلة المهام المتعددة"، حصلت WebAssembly.Memory على صيغة مشترَكة أيضًا. عند إنشاء الحزمة باستخدام علامة shared عبر واجهة برمجة تطبيقات JavaScript أو من خلال البرنامج الثنائي WebAssembly نفسه، يصبح برنامج تضمين حول SharedArrayBuffer بدلاً من ذلك. وهو نوع من ArrayBuffer يمكن مشاركته مع سلاسل محادثات أخرى وقراءته أو تعديله في الوقت نفسه من كلا الجانبين.

> new WebAssembly.Memory({ initial:1, maximum:10, shared:true }).buffer
SharedArrayBuffer {  }

على عكس postMessage، الذي يُستخدَم عادةً للتواصل بين الخيط الرئيسي وWeb Workers، SharedArrayBuffer لا يتطلّب نسخ البيانات أو حتى انتظار حلقة الأحداث لإرسال الرسائل واستلامها. بدلاً من ذلك، تظهر أي تغييرات لجميع سلاسل المحادثات بشكل فوري تقريبًا، ما يجعله هدف تجميع أفضل بكثير لعناصر المزامنة التقليدية.

لدى SharedArrayBuffer تاريخ معقد. تم طرحه في البداية في العديد من المتصفّحات في منتصف عام 2017، ولكن تم إيقافه في بداية عام 2018 بسبب اكتشاف ثغرات Spectre. كان السبب تحديدًا أنّ استخراج البيانات في Spectre يعتمد على هجمات التوقيت، أي قياس وقت تنفيذ قطعة معيّنة من التعليمات البرمجية. ولتصعيب هذا النوع من الهجمات، خفّضت المتصفّحات دقة واجهات برمجة التطبيقات العادية لتحديد الوقت، مثل Date.now وperformance.now. ومع ذلك، فإنّ الذاكرة المشتركة، بالإضافة إلى حلقة عداد بسيطة تعمل في سلسلة محادثات منفصلة، هي أيضًا طريقة موثوقة جدًا للحصول على دقة عالية في التوقيت، ومن الصعب جدًا التخفيف من تأثيرها بدون تقليل أداء وقت التشغيل بشكل كبير.

بدلاً من ذلك، أعاد الإصدار 68 من Chrome (أواسط عام 2018) تفعيل SharedArrayBuffer مرة أخرى من خلال الاستفادة من ميزة عزل المواقع الإلكترونية، وهي ميزة تضع المواقع الإلكترونية المختلفة في عمليات مختلفة وتصعِّب استخدام هجمات قناة جانبية، مثل Spectre. ومع ذلك، كان هذا الإجراء التخفيفي لا يزال يقتصر على متصفّح Chrome المتوافق مع أجهزة الكمبيوتر المكتبي فقط، لأنّ ميزة "عزل المواقع الإلكترونية" هي ميزة باهظة التكلفة إلى حدٍ ما، ولا يمكن تفعيلها تلقائيًا لجميع المواقع الإلكترونية على الأجهزة الجوّالة ذات الذاكرة المنخفضة، ولم يتم تنفيذها بعد من قِبل مورّدين آخرين.

في عام 2020، تمّت إتاحة ميزة "عزل المواقع الإلكترونية" في Chrome وFirefox، وأصبح بإمكان المواقع الإلكترونية تفعيلها باستخدام رؤوس COOP وCOEP. تتيح آلية الموافقة استخدام ميزة "عزل المواقع الإلكترونية" حتى على الأجهزة ذات الطاقة المنخفضة التي يكون فيها تفعيلها لجميع المواقع الإلكترونية باهظًا جدًا. للموافقة، أضِف العناوين التالية إلى المستند الرئيسي في إعدادات الخادم:

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

بعد تفعيل هذه الميزة، يمكنك الوصول إلى SharedArrayBuffer (بما في ذلك WebAssembly.Memory المستندة إلى SharedArrayBuffer) والموقّتات الدقيقة وقياس الذاكرة وواجهات برمجة التطبيقات الأخرى التي تتطلّب مصدرًا معزولًا لأسباب تتعلق بالأمان. اطّلِع على مقالة جعل موقعك الإلكتروني "معزولًا عن مصادر متعددة" باستخدام إطار عمل COOP وإطار عمل COEP للحصول على مزيد من التفاصيل.

وحدات WebAssembly الأساسية

على الرغم من أنّ SharedArrayBuffer تسمح لكل سلسلة محادثات بالقراءة والكتابة في الذاكرة نفسها، يجب التأكّد من عدم تنفيذ عمليات متضاربة في الوقت نفسه من أجل تبادل المعلومات بشكلٍ صحيح. على سبيل المثال، يمكن لسلسلة محادثات واحدة أن تبدأ في قراءة البيانات من عنوان مشترَك، بينما تكتب سلسلة محادثات أخرى فيها، وبالتالي ستحصل سلسلة المحادثات الأولى الآن على نتيجة فاسدة. تُعرف هذه الفئة من الأخطاء باسم شروط التسابق. لمنع حدوث حالات تعارض، عليك مزامنة عمليات الوصول هذه بطريقة ما. وهنا يأتي دور العمليات الذرية.

WebAssembly العمليات الذرّية هي إضافة إلى مجموعة تعليمات WebAssembly التي تسمح بقراءة خلايا صغيرة من البيانات (عادةً أعداد صحيحة بسعة 32 و64 بت) وكتابتها "بشكل ذرّي". وهذا يعني أنّه يتم ذلك بطريقة تضمن عدم قراءة أو كتابة سلسلتَي رسائل في الخلية نفسها في الوقت نفسه، ما يمنع حدوث هذه التعارضات على مستوى برمجي منخفض. بالإضافة إلى ذلك، تحتوي وحدات WebAssembly الذرية على نوعَين آخرين من التعليمات، وهما "الانتظار" و "الإشعار"، اللذان يسمحان لسلسلة مهام واحدة بالتوقف عن العمل ("الانتظار") على عنوان معيّن في ذاكرة مشترَكة إلى أن توقظها سلسلة مهام أخرى من خلال "الإشعار".

تستند جميع العناصر الأساسية لعمليات التزامن ذات المستوى الأعلى، بما في ذلك القنوات وملفات المزامنة وعمليات قفل القراءة والكتابة، إلى هذه التعليمات.

كيفية استخدام سلاسل مهام WebAssembly

رصد الميزات

إنّ وحدات WebAssembly الذرّية وSharedArrayBuffer هما ميزتان جديدتان نسبيًا ولا تتوفّران بعد في جميع المتصفحات المتوافقة مع WebAssembly. يمكنك الاطّلاع على المتصفّحات التي تتوافق مع ميزات WebAssembly الجديدة في خارطة الطريق على webassembly.org.

لضمان تمكّن جميع المستخدمين من تحميل تطبيقك، عليك تنفيذ ميزة التحسين التدريجي من خلال إنشاء نسختَين مختلفتَين من WebAssembly، إحداهما تتيح استخدام ميزة "تعدد المواضيع" والأخرى لا تتيحها. بعد ذلك، حمِّل الإصدار المتوافق استنادًا إلى نتائج رصد الميزات. لرصد إتاحة سلاسل WebAssembly أثناء التشغيل، استخدِم مكتبة wasm-feature-detect وحمِّل الوحدة على النحو التالي:

import { threads } from 'wasm-feature-detect';

const hasThreads = await threads();

const module = await (
  hasThreads
    ? import('./module-with-threads.js')
    : import('./module-without-threads.js')
);

// …now use `module` as you normally would

لنلقِ الآن نظرة على كيفية إنشاء إصدار متعدّد المواضيع من وحدة WebAssembly.

C

في لغة C، وخصوصًا على الأنظمة المشابهة لنظام التشغيل Unix، تكون الطريقة الشائعة لاستخدام مؤشرات الترابط هي من خلال POSIX Threads التي تقدّمها مكتبة pthread. يوفّر Emscripten تنفيذًا متوافقًا مع واجهة برمجة التطبيقات ل مكتبة pthread التي تم إنشاؤها على Web Workers والذاكرة المشتركة والعمليات الذرية، بحيث يمكن للرمز البرمجي نفسه أن يعمل على الويب بدون تغييرات.

لنلقِ نظرة على مثال:

example.c:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void *thread_callback(void *arg)
{
    sleep(1);
    printf("Inside the thread: %d\n", *(int *)arg);
    return NULL;
}

int main()
{
    puts("Before the thread");

    pthread_t thread_id;
    int arg = 42;
    pthread_create(&thread_id, NULL, thread_callback, &arg);

    pthread_join(thread_id, NULL);

    puts("After the thread");

    return 0;
}

في ما يلي رؤوس مكتبة pthread التي تم تضمينها من خلال pthread.h. يمكنك أيضًا الاطّلاع على بضع دوالّ مهمة للتعامل مع سلاسل المحادثات.

سينشئ pthread_create سلسلسة محادثات في الخلفية. تأخذ هذه الوظيفة وجهة لتخزين معرّف سلسلة محادثات فيها، وبعض سمات إنشاء ملف المناقشة (هنا لا يتم تمرير أي منها، لذا فهي مجرد NULL)، ودالّة الاستدعاء التي سيتم تنفيذها في ملف المناقشة الجديد (هنا thread_callback)، ومؤشر اختياري للوسيطة يتم تمريره إلى دالّة الاستدعاء هذه في حال أردت مشاركة بعض البيانات من سلسلة المحادثات الرئيسية. في هذا المثال، نشارك مؤشرًا إلى المتغيّر arg.

يمكن استدعاء pthread_join لاحقًا في أي وقت للانتظار إلى أن تنتهي سلسلة التعليمات من التنفيذ، والحصول على النتيجة التي تم إرجاعها من دالة الاستدعاء. ويقبل هذا الإجراء معرّف الخيط الذي تمّ تعيينه سابقًا بالإضافة إلى مؤشر لتخزين النتيجة. في هذه الحالة، لا تتوفّر أي نتائج، لذا تأخذ الدالة NULL كوسيطة.

لتجميع الرمز باستخدام مؤشرات الترابط مع Emscripten، عليك استدعاء emcc وضبط مَعلمة -pthread ، كما هو الحال عند تجميع الرمز نفسه باستخدام Clang أو GCC على منصات أخرى:

emcc -pthread example.c -o example.js

ومع ذلك، عند محاولة تشغيله في متصفّح أو Node.js، سيظهر لك تحذير ثم سيتوقف البرنامج عن العمل:

Before the thread
Tried to spawn a new thread, but the thread pool is exhausted.
This might result in a deadlock unless some threads eventually exit or the code
explicitly breaks out to the event loop.
If you want to increase the pool size, use setting `-s PTHREAD_POOL_SIZE=...`.
If you want to throw an explicit error instead of the risk of deadlocking in those
cases, use setting `-s PTHREAD_POOL_SIZE_STRICT=2`.
[…hangs here…]

ما السبب؟ تكمن المشكلة في أنّ معظم واجهات برمجة التطبيقات التي تستغرق وقتًا طويلاً على الويب غير متزامنة وتعتمد على حلقة الأحداث لتنفيذها. يُعدّ هذا القيد فرقًا مهمًا مقارنةً بالبيئة التقليدية ، حيث تعمل التطبيقات عادةً على نقل البيانات بطريقة متزامنة وبشكل حظر. يمكنك الاطّلاع على مقالة المدونة حول استخدام واجهات برمجة تطبيقات الويب غير المتزامنة من WebAssembly إذا أردت معرفة مزيد من المعلومات.

في هذه الحالة، يستدعي الرمز البرمجي pthread_create بشكل متزامن لإنشاء سلسلة مهام في الخلفية، ويتابع ذلك بمكالمة أخرى متزامنة إلى pthread_join تنتظر انتهاء تنفيذ سلسلة المهام في الخلفية. ومع ذلك، فإنّ Web Workers التي يتم استخدامها في الكواليس عند تجميع هذه التعليمات البرمجية باستخدام Emscripten تكون غير متزامنة. وبالتالي، ما يحدث هو أنّ pthread_create يجدول فقط بدء سلسلتَي رسائل Worker جديدة في دورة الأحداث التالية، ولكن بعد ذلك يحظر pthread_join دورة الأحداث على الفور للانتظار إلى أن يصبح Worker متاحًا، وبالتالي يمنع إنشاءه. وهذا مثال كلاسيكي على التوقف المفاجئ.

تتمثل إحدى طرق حلّ هذه المشكلة في إنشاء مجموعة من "العمال" مسبقًا، قبل بدء البرنامج. عند استدعاء pthread_create، يمكنه أخذ عامل جاهز للاستخدام من المجموعة، وتنفيذ ردّ الاتصال المقدَّم في سلسلة المهام الخلفية، وإرجاع العامل إلى المجموعة. يمكن تنفيذ كل ذلك بشكل متزامن، ولن تحدث أي عمليات حظر طالما أنّ المجموعة كبيرة بما يكفي.

وهذا هو ما يسمح به Emscripten تمامًا باستخدام الخيار -s PTHREAD_POOL_SIZE=.... يسمح هذا الخيار بتحديد عدد سلاسل المهام، إما عدد ثابت أو تعبير JavaScript مثل navigator.hardwareConcurrency لإنشاء عدد سلاسل مهام يساوي عدد النوى في وحدة المعالجة المركزية. يكون الخيار الأخير مفيدًا عندما يمكن لرمزك التوسّع إلى عدد عشوائي من سلاسل المهام.

في المثال أعلاه، يتم إنشاء سلسلة محادثات واحدة فقط، لذا بدلاً من حجز جميع النوى، يكفي استخدام -s PTHREAD_POOL_SIZE=1:

emcc -pthread -s PTHREAD_POOL_SIZE=1 example.c -o example.js

هذه المرة، عند تنفيذه، تنجح الإجراءات:

Before the thread
Inside the thread: 42
After the thread
Pthread 0x701510 exited.

هناك مشكلة أخرى: هل ترى sleep(1) في مثال الرمز؟ يتم تنفيذه في سلسلت التعليمات المخصّصة للردّ، أي خارج سلسلة التعليمات الرئيسية، لذا من المفترض أن يكون الأمر على ما يرام، أليس كذلك؟ حسنًا، ليس الأمر كذلك.

عند استدعاء pthread_join، يجب الانتظار حتى انتهاء تنفيذ سلسلة التعليمات، ما يعني أنّه إذا كانت سلسلة التعليمات التي تم إنشاؤها تُنفِّذ مهام تستغرق وقتًا طويلاً، في هذه الحالة، سيتم إيقاف السلسلة الرئيسية أيضًا لمدة الوقت نفسها إلى أن تظهر النتائج. عند تنفيذ ملف JS هذا في المتصفّح، سيتم حظر سلسلة واجهة المستخدم لمدة ثانية واحدة إلى أن يعود المرجع المخصّص لسلسلة المهام. ويؤدي ذلك إلى تقديم تجربة سيئة للمستخدم.

هناك بضعة حلول لهذا:

  • pthread_detach
  • -s PROXY_TO_PTHREAD
  • Worker وComlink المخصّصان

pthread_detach

أولاً، إذا كنت بحاجة فقط إلى تنفيذ بعض المهام خارج سلسلة المهام الرئيسية، ولكن ليس عليك الانتظار للحصول على النتائج، يمكنك استخدام pthread_detach بدلاً من pthread_join. سيؤدي ذلك إلى إبقاء دالة الاستدعاء الخاصة بالسلسلة قيد التشغيل في الخلفية. إذا كنت تستخدم هذا الخيار، يمكنك إيقاف التحذير باستخدام الرمز -s PTHREAD_POOL_SIZE_STRICT=0.

PROXY_TO_PTHREAD

ثانيًا، إذا كنت تُجمِّع تطبيقًا مكتوبًا بلغة C بدلاً من مكتبة، يمكنك استخدام الخيار -s PROXY_TO_PTHREAD، الذي سينقل شفرة التطبيق الرئيسية إلى سلسلة محادثات منفصلة بالإضافة إلى أي سلاسل محادثات متداخلة أنشأها التطبيق نفسه. بهذه الطريقة، يمكن للرمز البرمجي الرئيسي حظر المحتوى بأمان في أي وقت بدون تجميد واجهة المستخدم. عند استخدام هذا الخيار، ليس عليك أيضًا إنشاء مجموعة مؤشرات الترابط مسبقًا. بدلاً من ذلك، يمكن لـ Emscripten الاستفادة من سلسلة المهام الرئيسية لإنشاء "عمال" جدد في الخلفية، ثم حظر سلسلة المهام المساعِدة في pthread_join بدون حدوث "توقّف عمليات المعالجة".

ثالثًا، إذا كنت تعمل على مكتبة ولا تزال بحاجة إلى الحظر، يمكنك إنشاء Worker الخاص بك، استيراد الرمز الذي تم إنشاؤه باستخدام Emscripten وعرضه باستخدام Comlink في الخيط الرئيسي. ستتمكّن سلسلة التعليمات الرئيسية من استدعاء أي طرق تم تصديرها كدوالّ غير متزامنة، وسيؤدي ذلك أيضًا إلى تجنُّب حظر واجهة المستخدم.

في تطبيق بسيط مثل المثال السابق، يكون -s PROXY_TO_PTHREAD هو الخيار الأفضل:

emcc -pthread -s PROXY_TO_PTHREAD example.c -o example.js

C++‎

تنطبق جميع التحذيرات والمنطق نفسه على C++ بالطريقة نفسها. والشيء الجديد الوحيد الذي يمكنك الحصول عليه هو الوصول إلى واجهات برمجة تطبيقات ذات مستوى أعلى، مثل std::thread و std::async، اللتين تستخدمان مكتبة pthread التي سبق أن ناقشناها.

وبالتالي، يمكن إعادة كتابة المثال أعلاه بلغة C++ أكثر شيوعًا على النحو التالي:

example.cpp:

#include <iostream>
#include <thread>
#include <chrono>

int main()
{
    puts("Before the thread");

    int arg = 42;
    std::thread thread([&]() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Inside the thread: " << arg << std::endl;
    });

    thread.join();

    std::cout << "After the thread" << std::endl;

    return 0;
}

عند تجميعه وتنفيذه باستخدام مَعلمات مشابهة، سيتصرف بالطريقة نفسها التي يتصرف بها مثال C:

emcc -std=c++11 -pthread -s PROXY_TO_PTHREAD example.cpp -o example.js

إخراج:

Before the thread
Inside the thread: 42
Pthread 0xc06190 exited.
After the thread
Proxied main thread 0xa05c18 finished with return code 0. EXIT_RUNTIME=0 set, so
keeping main thread alive for asynchronous event operations.
Pthread 0xa05c18 exited.

Rust

على عكس Emscripten، لا يتضمّن Rust هدفًا مخصّصًا للويب من البداية إلى النهاية، بل يقدّم بدلاً من ذلك هدفًا عامًا wasm32-unknown-unknown لإخراج WebAssembly العام.

إذا كان من المخطَّط استخدام Wasm في بيئة ويب، يتم ترك أي تفاعل مع واجهات برمجة تطبيقات JavaScript ل المكتبات والأدوات الخارجية، مثل wasm-bindgen وwasm-pack. ويعني ذلك أنّ المكتبة العادية لا تعرف Web Workers ولن تعمل واجهات برمجة التطبيقات العادية، مثل std::thread، عند تجميعها إلى WebAssembly.

لحسن الحظ، تعتمد معظم المنظومة المتكاملة على مكتبات ذات مستوى أعلى لإدارة معالجة المهام المتعددة. في هذا المستوى، من الأسهل بكثير تجاهل جميع الاختلافات بين المنصات.

على وجه الخصوص، Rayon هو الخيار الأكثر شيوعًا لمعالجة المهام المتوازية للبيانات في Rust. يتيح لك ذلك استخدام سلاسل الطرق في الدوالّ المتكرّرة العادية، وعادةً ما يتم ذلك من خلال تغيير سطر واحد، وتحويلها بطريقة يتم فيها تشغيلها بشكل موازٍ على جميع سلاسل المهام المتاحة بدلاً من تشغيلها بشكل تسلسلي. على سبيل المثال:

pub fn sum_of_squares(numbers: &[i32]) -> i32 {
  numbers
  .iter()
  .par_iter()
  .map(|x| x * x)
  .sum()
}

باستخدام هذا التغيير البسيط، ستقسّم التعليمات البرمجية بيانات الإدخال، وتحسب x * x والمجاميع الجزئية في مناقشات نظرية متوازية، وفي النهاية تضيف هذه النتائج الجزئية معًا.

للتكيّف مع الأنظمة الأساسية التي لا تتضمّن std::thread، يوفّر Rayon أدوات تتيح تحديد منطق مخصّص لإنشاء سلاسل المهام والخروج منها.

يستفيد wasm-bindgen-rayon من هذه العناصر لإنشاء خيوط WebAssembly كمشغّلات ويب. لاستخدامه، عليك إضافته كعنصر تابع و اتّباع خطوات الضبط الموضّحة في المستندات. سيظهر المثال أعلاه على النحو التالي:

pub use wasm_bindgen_rayon::init_thread_pool;

#[wasm_bindgen]
pub fn sum_of_squares(numbers: &[i32]) -> i32 {
  numbers
  .par_iter()
  .map(|x| x * x)
  .sum()
}

بعد الانتهاء، ستُصدِر لغة JavaScript التي تم إنشاؤها دالة initThreadPool إضافية. ستنشئ هذه الدالة مجموعة من "العمال" وستعيد استخدامها طوال مدة البرنامج لأي عمليات متعددة المواضيع يجريها Rayon.

تشبه آلية حوض التخزين هذه خيار -s PTHREAD_POOL_SIZE=... في Emscripten الذي شرحناه سابقًا، ويجب أيضًا إعدادها قبل الرمز البرمجي الرئيسي لتجنُّب حالات التوقف المفاجئ:

import init, { initThreadPool, sum_of_squares } from './pkg/index.js';

// Regular wasm-bindgen initialization.
await init();

// Thread pool initialization with the given number of threads
// (pass `navigator.hardwareConcurrency` if you want to use all cores).
await initThreadPool(navigator.hardwareConcurrency);

// ...now you can invoke any exported functions as you normally would
console.log(sum_of_squares(new Int32Array([1, 2, 3]))); // 14

يُرجى العِلم أنّ الاحتياطات نفسها بشأن حظر سلسلة التعليمات الرئيسية تنطبق هنا أيضًا. حتى في مثال sum_of_squares، يجب حظر سلسلة التعليمات الرئيسية للانتظار إلى أن تظهر النتائج الجزئية من سلاسل التعليمات الأخرى.

قد يكون وقت الانتظار قصيرًا جدًا أو طويلاً، وذلك حسب تعقيد الدوالّ المتكرّرة وعدد السلسلات المتاحة، ولكن لضمان الأمان، تمنع محرّكات المتصفّحات بشكل نشط حظر السلسلة الأساسية بالكامل، وسيؤدي هذا الرمز إلى ظهور خطأ. بدلاً من ذلك، عليك إنشاء Worker واستيراد الرمز البرمجي الذي تم إنشاؤه باستخدام wasm-bindgen وعرض واجهة برمجة التطبيقات باستخدام مكتبة مثل Comlink في سلسلة المهام الرئيسية.

اطّلِع على مثال wasm-bindgen-rayon للحصول على تعليمات برمجية توضيحية كاملة تعرض ما يلي:

حالات الاستخدام في الحياة الواقعية

نستخدم بشكل نشط مؤشرات WebAssembly في Squoosh.app لضغط الصور من جهة العميل، وعلى وجه الخصوص، لتنسيقات مثل AVIF (C++) وJPEG-XL (C++) وOxiPNG (Rust) وWebP v2 (C++). وبفضل تعدد مؤشرات المعالجة فقط، حققنا سرعات متسقة تتراوح بين 1.5 و3 أضعاف (تختلف النسبة الدقيقة حسب برنامج الترميز)، وتمكّنا من زيادة هذه الأرقام بشكل أكبر من خلال الجمع بين مؤشرات WebAssembly وWebAssembly SIMD.

Google Earth هي خدمة بارزة أخرى تستخدم سلاسل مهام WebAssembly في إصدارها المتوافق مع الويب.

FFMPEG.WASM هو إصدار WebAssembly من سلسلة أدوات FFmpeg الرائجة لمعالجة الوسائط المتعددة التي تستخدم سلاسل مهام WebAssembly لتشفير الفيديوهات مباشرةً في المتصفّح بكفاءة.

هناك العديد من الأمثلة الأخرى المثيرة للاهتمام التي تستخدم سلاسل مهام WebAssembly. احرص على الاطّلاع على العروض التوضيحية ونشر تطبيقاتك ومكاتبك التي تستخدم خيوطًا متعددة على الويب.