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

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

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

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

آلية عمل سلاسل محادثات WebAssembly

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

عمال الويب

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

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

SharedArrayBuffer

يتم تمثيل ذاكرة WebAssembly بواسطة كائن WebAssembly.Memory في واجهة برمجة تطبيقات JavaScript. وبشكلٍ تلقائي، تكون 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. ومع ذلك، فإنّ الذاكرة المشتركة، بالإضافة إلى حلقة عداد بسيطة تعمل في سلسلة محادثات منفصلة، هي أيضًا طريقة موثوقة جدًا للحصول على دقة عالية في التوقيت، ومن الصعب جدًا التخفيف من تأثيرها بدون تقليل أداء وقت التشغيل بشكل كبير.

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

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

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

رصد الميزات

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

لضمان تمكّن جميع المستخدمين من تحميل تطبيقك، عليك تنفيذ ميزة التحسين التدريجي من خلال إنشاء نسختَين مختلفتَين من Wasm، إحداهما تتيح استخدام ميزة "تعدد المواضيع" والأخرى لا تتيحها. بعد ذلك، حمِّل الإصدار المتوافق استنادًا إلى نتائج رصد الميزات. لاكتشاف دعم سلاسل 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 على الفور حلقة الحدث لانتظار هذا العامل، ما يؤدي إلى منع إنشائها على الإطلاق. وهذا مثال كلاسيكي على التوقف المفاجئ.

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

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

في المثال أعلاه، يتم إنشاء سلسلة محادثات واحدة فقط، لذا بدلاً من حجز جميع النوى، يكفي استخدام -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 إضافية. ستنشئ هذه الدالة مجموعة من &quot;العمال&quot; وستعيد استخدامها طوال مدة البرنامج لأي عمليات متعددة المواضيع يجريها 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++ ). فبفضل استخدام الترميز المتعدد السلاسل معًا (C++ )، شهدنا زيادة بمقدار 1.5x و3x

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

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

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