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

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

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

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

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

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

عمال الويب

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

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

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

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

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

رصد الميزات

تُعدّ مكوّنات WebAssembly Atom وSharedArrayBuffer من الميزات الجديدة نسبيًا وغير متاحة بعد في جميع المتصفّحات المتوافقة مع WebAssembly. يمكنك العثور على المتصفِّحات التي تتوافق مع ميزات WebAssembly الجديدة في خارطة طريق webassembly.org.

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

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…]

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

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

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

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

  • pthread_detach
  • -s PROXY_TO_PTHREAD
  • عامل مخصص ورابط Comlink

pthread_detach

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

PROXY_TO_PTHREAD

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

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

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 إضافية. ستنشئ هذه الدالة مجموعة من العاملين وستعيد استخدامها طوال فترة البرنامج لأي عمليات متعددة السلاسل يجريها رايون.

تتشابه آلية التجميع هذه مع خيار -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 لا يزال بحاجة إلى حظر سلسلة التعليمات الرئيسية من أجل انتظار النتائج الجزئية من سلاسل المحادثات الأخرى.

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

اطّلِع على مثال Wasm-bindgen-rayon للاطّلاع على عرض توضيحي شامل:

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

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

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

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

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