WebAssembly-এ অন্যান্য ভাষায় লিখিত মাল্টিথ্রেড অ্যাপ্লিকেশনগুলি কীভাবে আনতে হয় তা শিখুন।
WebAssembly থ্রেড সমর্থন হল WebAssembly-এর সবচেয়ে গুরুত্বপূর্ণ কর্মক্ষমতা সংযোজনগুলির মধ্যে একটি। এটি আপনাকে হয় আপনার কোডের অংশগুলিকে পৃথক কোরে সমান্তরালভাবে চালানোর অনুমতি দেয়, অথবা ইনপুট ডেটার স্বাধীন অংশগুলিতে একই কোড ব্যবহার করতে দেয়, এটি ব্যবহারকারীর যতগুলি কোরে আছে তা স্কেল করে এবং সামগ্রিকভাবে কার্যকর করার সময় উল্লেখযোগ্যভাবে হ্রাস করে।
এই নিবন্ধে আপনি C, C++, এবং Rust-এর মতো ভাষায় লিখিত মাল্টিথ্রেডেড অ্যাপ্লিকেশনগুলিকে ওয়েবে আনতে কিভাবে WebAssembly থ্রেড ব্যবহার করবেন তা শিখবেন।
কিভাবে WebAssembly থ্রেড কাজ করে
WebAssembly থ্রেডগুলি একটি পৃথক বৈশিষ্ট্য নয়, তবে বেশ কয়েকটি উপাদানের সংমিশ্রণ যা WebAssembly অ্যাপগুলিকে ওয়েবে ঐতিহ্যগত মাল্টিথ্রেডিং দৃষ্টান্ত ব্যবহার করতে দেয়।
ওয়েব কর্মী
প্রথম উপাদান হল নিয়মিত কর্মী যাকে আপনি জাভাস্ক্রিপ্ট থেকে জানেন এবং ভালবাসেন৷ WebAssembly থ্রেড নতুন অন্তর্নিহিত থ্রেড তৈরি করতে new Worker
কনস্ট্রাক্টর ব্যবহার করে। প্রতিটি থ্রেড একটি জাভাস্ক্রিপ্ট আঠালো লোড করে, এবং তারপরে মূল থ্রেডটি Worker#postMessage
পদ্ধতি ব্যবহার করে সংকলিত WebAssembly.Module
পাশাপাশি শেয়ার করা WebAssembly.Memory
(নীচে দেখুন) সেই অন্যান্য থ্রেডগুলির সাথে ভাগ করে নেয়৷ এটি যোগাযোগ স্থাপন করে এবং সেই সমস্ত থ্রেডকে একই শেয়ার করা মেমরিতে জাভাস্ক্রিপ্টের মাধ্যমে আবার না গিয়ে একই WebAssembly কোড চালানোর অনুমতি দেয়।
ওয়েব ওয়ার্কাররা এখন এক দশকেরও বেশি সময় ধরে রয়েছে, ব্যাপকভাবে সমর্থিত এবং তাদের কোনো বিশেষ পতাকার প্রয়োজন নেই।
SharedArrayBuffer
WebAssembly মেমরি JavaScript API-এ WebAssembly.Memory
অবজেক্ট দ্বারা প্রতিনিধিত্ব করা হয়। ডিফল্টরূপে WebAssembly.Memory
হল একটি ArrayBuffer
চারপাশে একটি মোড়ক - একটি কাঁচা বাইট বাফার যা শুধুমাত্র একটি থ্রেড দ্বারা অ্যাক্সেস করা যায়।
> new WebAssembly.Memory({ initial:1, maximum:10 }).buffer
ArrayBuffer { … }
মাল্টিথ্রেডিং সমর্থন করার জন্য, WebAssembly.Memory
একটি ভাগ করা বৈকল্পিকও অর্জন করেছে। JavaScript API এর মাধ্যমে অথবা WebAssembly বাইনারি দ্বারা একটি shared
পতাকা তৈরি করা হলে, এটি পরিবর্তে একটি SharedArrayBuffer
চারপাশে একটি মোড়ক হয়ে যায়। এটি ArrayBuffer
এর একটি বৈচিত্র যা অন্যান্য থ্রেডের সাথে ভাগ করা যায় এবং উভয় দিক থেকে একই সাথে পড়া বা পরিবর্তন করা যায়।
> new WebAssembly.Memory({ initial:1, maximum:10, shared:true }).buffer
SharedArrayBuffer { … }
postMessage
বিপরীতে, সাধারণত প্রধান থ্রেড এবং ওয়েব ওয়ার্কারদের মধ্যে যোগাযোগের জন্য ব্যবহৃত হয়, SharedArrayBuffer
ডেটা অনুলিপি করার প্রয়োজন হয় না বা এমনকি বার্তা পাঠানো এবং গ্রহণ করার জন্য ইভেন্ট লুপের জন্য অপেক্ষা করে না। পরিবর্তে, সমস্ত থ্রেড প্রায় তাৎক্ষণিকভাবে যেকোন পরিবর্তন দেখা যায়, যা এটিকে প্রথাগত সিঙ্ক্রোনাইজেশন আদিমগুলির জন্য আরও ভাল সংকলন লক্ষ্য করে তোলে।
SharedArrayBuffer
একটি জটিল ইতিহাস আছে। এটি প্রাথমিকভাবে 2017 সালের মাঝামাঝি বেশ কয়েকটি ব্রাউজারে পাঠানো হয়েছিল, কিন্তু স্পেকটার দুর্বলতা আবিষ্কারের কারণে 2018 সালের শুরুতে এটি নিষ্ক্রিয় করতে হয়েছিল। বিশেষ কারণ ছিল যে স্পেকটারে ডেটা নিষ্কাশন টাইমিং আক্রমণের উপর নির্ভর করে-কোডের একটি নির্দিষ্ট অংশের সম্পাদনের সময় পরিমাপ করা। এই ধরনের আক্রমণকে আরও কঠিন করার জন্য, ব্রাউজারগুলি Date.now
এবং performance.now
মতো স্ট্যান্ডার্ড টাইমিং API-এর নির্ভুলতা হ্রাস করেছে। যাইহোক, ভাগ করা মেমরি, একটি পৃথক থ্রেডে চলমান একটি সাধারণ কাউন্টার লুপের সাথে মিলিত উচ্চ-নির্ভুল সময় পাওয়ার জন্য একটি খুব নির্ভরযোগ্য উপায় , এবং রানটাইম কার্যকারিতা উল্লেখযোগ্যভাবে থ্রটলিং না করে প্রশমিত করা অনেক কঠিন।
পরিবর্তে, Chrome 68 (2018-এর মাঝামাঝি) সাইট আইসোলেশনের সুবিধার মাধ্যমে SharedArrayBuffer
আবার-সক্ষম করেছে — এমন একটি বৈশিষ্ট্য যা বিভিন্ন ওয়েবসাইটকে বিভিন্ন প্রক্রিয়ায় রাখে এবং স্পেকটারের মতো পার্শ্ব-চ্যানেল আক্রমণগুলি ব্যবহার করা আরও কঠিন করে তোলে। যাইহোক, এই প্রশমনটি এখনও শুধুমাত্র Chrome ডেস্কটপের মধ্যেই সীমাবদ্ধ ছিল, কারণ সাইট আইসোলেশন একটি মোটামুটি ব্যয়বহুল বৈশিষ্ট্য, এবং কম মেমরির মোবাইল ডিভাইসে সমস্ত সাইটের জন্য ডিফল্টরূপে সক্ষম করা যায়নি বা এটি এখনও অন্যান্য বিক্রেতাদের দ্বারা প্রয়োগ করা হয়নি৷
2020-এ ফাস্ট-ফরওয়ার্ড, ক্রোম এবং ফায়ারফক্স উভয়েরই সাইট আইসোলেশনের বাস্তবায়ন রয়েছে, এবং ওয়েবসাইটগুলির জন্য COOP এবং COEP শিরোনাম সহ বৈশিষ্ট্যটিতে অপ্ট-ইন করার একটি আদর্শ উপায় রয়েছে৷ একটি অপ্ট-ইন মেকানিজম সাইট আইসোলেশন ব্যবহার করার অনুমতি দেয় এমনকি কম-পাওয়ার ডিভাইসগুলিতেও যেখানে সমস্ত ওয়েবসাইটের জন্য এটি সক্ষম করা খুব ব্যয়বহুল হবে৷ অপ্ট-ইন করতে, আপনার সার্ভার কনফিগারেশনের প্রধান নথিতে নিম্নলিখিত শিরোনামগুলি যুক্ত করুন:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
একবার আপনি অপ্ট-ইন করলে, আপনি SharedArrayBuffer
এ অ্যাক্সেস পাবেন (একটি SharedArrayBuffer
দ্বারা সমর্থিত WebAssembly.Memory
সহ), সুনির্দিষ্ট টাইমার, মেমরি পরিমাপ এবং অন্যান্য API যেগুলির নিরাপত্তার কারণে একটি বিচ্ছিন্ন উত্স প্রয়োজন৷ আরও বিশদ বিবরণের জন্য COOP এবং COEP ব্যবহার করে আপনার ওয়েবসাইটকে "ক্রস-অরিজিন আইসোলেটেড" তৈরি করা দেখুন।
WebAssembly পরমাণু
যদিও SharedArrayBuffer
প্রতিটি থ্রেডকে একই মেমরিতে পড়তে এবং লিখতে দেয়, সঠিক যোগাযোগের জন্য আপনি নিশ্চিত করতে চান যে তারা একই সময়ে বিরোধপূর্ণ ক্রিয়াকলাপগুলি সম্পাদন করে না। উদাহরণস্বরূপ, একটি থ্রেডের পক্ষে একটি ভাগ করা ঠিকানা থেকে ডেটা পড়া শুরু করা সম্ভব, যখন অন্য একটি থ্রেড এটিতে লিখছে, তাই প্রথম থ্রেডটি এখন একটি দূষিত ফলাফল পাবে। এই শ্রেণীর বাগগুলি রেস কন্ডিশন হিসাবে পরিচিত। জাতি পরিস্থিতি প্রতিরোধ করার জন্য, আপনাকে সেই অ্যাক্সেসগুলিকে একরকম সিঙ্ক্রোনাইজ করতে হবে। এখানেই পারমাণবিক অপারেশন আসে।
WebAssembly পরমাণু হল WebAssembly নির্দেশনা সেটের একটি এক্সটেনশন যা ডেটার ছোট কোষগুলিকে (সাধারণত 32- এবং 64-বিট পূর্ণসংখ্যা) "পারমাণবিকভাবে" পড়তে এবং লিখতে দেয়। অর্থাৎ, এমন একটি উপায় যা গ্যারান্টি দেয় যে একই সময়ে একই ঘরে দুটি থ্রেড পড়া বা লেখা হচ্ছে না, নিম্ন স্তরে এই ধরনের দ্বন্দ্ব প্রতিরোধ করে। উপরন্তু, WebAssembly পরমাণুতে আরও দুটি নির্দেশের ধরন রয়েছে—"অপেক্ষা করুন" এবং "বিজ্ঞাপন"—যা একটি শেয়ার্ড মেমরিতে একটি প্রদত্ত ঠিকানায় একটি থ্রেডকে ঘুমোতে দেয় ("অপেক্ষা করুন") যতক্ষণ না অন্য একটি থ্রেড "বিজ্ঞপ্তি" এর মাধ্যমে এটিকে জাগিয়ে তোলে।
চ্যানেল, মিউটেক্স এবং রিড-রাইট লক সহ সমস্ত উচ্চ-স্তরের সিঙ্ক্রোনাইজেশন আদিম এই নির্দেশাবলীর উপর ভিত্তি করে তৈরি করা হয়।
কিভাবে WebAssembly থ্রেড ব্যবহার করবেন
বৈশিষ্ট্য সনাক্তকরণ
WebAssembly পরমাণু এবং SharedArrayBuffer
তুলনামূলকভাবে নতুন বৈশিষ্ট্য এবং WebAssembly সমর্থন সহ সমস্ত ব্রাউজারে এখনও উপলব্ধ নয়। আপনি webassembly.org রোডম্যাপে কোন ব্রাউজারগুলি নতুন WebAssembly বৈশিষ্ট্যগুলিকে সমর্থন করে তা খুঁজে পেতে পারেন৷
সমস্ত ব্যবহারকারী আপনার অ্যাপ্লিকেশনটি লোড করতে পারে তা নিশ্চিত করার জন্য, আপনাকে Wasm-এর দুটি ভিন্ন সংস্করণ তৈরি করে প্রগতিশীল বর্ধন প্রয়োগ করতে হবে—একটি মাল্টিথ্রেডিং সমর্থন সহ এবং একটি ছাড়া। তারপর বৈশিষ্ট্য সনাক্তকরণ ফলাফলের উপর নির্ভর করে সমর্থিত সংস্করণ লোড করুন। রানটাইমে WebAssembly থ্রেড সমর্থন সনাক্ত করতে, wasm-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 মডিউলের একটি মাল্টিথ্রেডেড সংস্করণ তৈরি করা যায়।
গ
সি-তে, বিশেষ করে ইউনিক্স-এর মতো সিস্টেমে, থ্রেডগুলি ব্যবহার করার সাধারণ উপায় হল pthread
লাইব্রেরি দ্বারা প্রদত্ত POSIX থ্রেডের মাধ্যমে। Emscripten ওয়েব ওয়ার্কার্স, শেয়ার করা মেমরি এবং পরমাণুর উপরে নির্মিত pthread
লাইব্রেরির একটি API-সামঞ্জস্যপূর্ণ বাস্তবায়ন প্রদান করে , যাতে একই কোড পরিবর্তন ছাড়াই ওয়েবে কাজ করতে পারে।
আসুন একটি উদাহরণ দেখে নেওয়া যাক:
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
প্যারামিটার পাস করতে হবে, যেমন একই কোড ক্ল্যাং বা 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…]
কি হয়েছে? সমস্যা হল, ওয়েবে বেশির ভাগ সময়-সাপেক্ষ APIগুলি অ্যাসিঙ্ক্রোনাস এবং চালানোর জন্য ইভেন্ট লুপের উপর নির্ভর করে। প্রথাগত পরিবেশের তুলনায় এই সীমাবদ্ধতা একটি গুরুত্বপূর্ণ পার্থক্য, যেখানে অ্যাপ্লিকেশনগুলি সাধারণত I/O সিঙ্ক্রোনাস, ব্লকিং পদ্ধতিতে চালায়। আপনি যদি আরও জানতে চান তাহলে WebAssembly থেকে অ্যাসিঙ্ক্রোনাস ওয়েব API ব্যবহার করার বিষয়ে ব্লগ পোস্টটি দেখুন।
এই ক্ষেত্রে, কোডটি একটি ব্যাকগ্রাউন্ড থ্রেড তৈরি করতে সিঙ্ক্রোনাসভাবে pthread_create
আমন্ত্রণ জানায় এবং pthread_join
এ অন্য একটি সিঙ্ক্রোনাস কলের মাধ্যমে অনুসরণ করে যা ব্যাকগ্রাউন্ড থ্রেডটি কার্যকর করার জন্য অপেক্ষা করে। যাইহোক, ওয়েব ওয়ার্কার, যেগুলি পর্দার আড়ালে ব্যবহৃত হয় যখন এই কোডটি Emscripten-এর সাথে সংকলিত হয়, তারা অ্যাসিঙ্ক্রোনাস। তাহলে যা ঘটে তা হল, pthread_create
পরবর্তী ইভেন্ট লুপ রানে তৈরি করার জন্য শুধুমাত্র একটি নতুন Worker থ্রেড তৈরি করার সময়সূচী করে , কিন্তু তারপর pthread_join
সেই ওয়ার্কারের জন্য অপেক্ষা করার জন্য ইভেন্ট লুপটিকে অবিলম্বে ব্লক করে, এবং এটি করে এটি তৈরি হওয়া থেকে বাধা দেয়। এটি একটি অচলাবস্থার একটি ক্লাসিক উদাহরণ।
এই সমস্যা সমাধানের একটি উপায় হল কর্মসূচী শুরু হওয়ার আগেই সময়ের আগে কর্মীদের একটি পুল তৈরি করা। যখন pthread_create
আমন্ত্রণ করা হয়, এটি পুল থেকে ব্যবহার করার জন্য প্রস্তুত কর্মী নিতে পারে, এর ব্যাকগ্রাউন্ড থ্রেডে প্রদত্ত কলব্যাক চালাতে পারে এবং কর্মীকে পুলে ফিরিয়ে দিতে পারে। এই সমস্তগুলি সিঙ্ক্রোনাসভাবে করা যেতে পারে, তাই পুলটি যথেষ্ট বড় না হওয়া পর্যন্ত কোনও অচলাবস্থা থাকবে না।
এমস্ক্রিপ্টেন -s PTHREAD_POOL_SIZE=...
বিকল্পের সাথে ঠিক এটিই অনুমতি দেয়। এটি সিপিইউতে যতগুলি কোর রয়েছে ততগুলি থ্রেড তৈরি করতে এটি অনেকগুলি থ্রেড-হয় একটি নির্দিষ্ট সংখ্যা, বা 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
কল করা হয়, তখন এটি থ্রেড এক্সিকিউশন শেষ হওয়ার জন্য অপেক্ষা করতে হবে, যার অর্থ যদি তৈরি করা থ্রেড দীর্ঘ-চলমান কাজগুলি সম্পাদন করে- এই ক্ষেত্রে, 1 সেকেন্ড ঘুমাচ্ছে- তাহলে মূল থ্রেডটিকেও একই পরিমাণে ব্লক করতে হবে। ফলাফল ফিরে আসা পর্যন্ত সময়। যখন এই JS ব্রাউজারে কার্যকর করা হয়, তখন থ্রেড কলব্যাক ফিরে না আসা পর্যন্ত এটি UI থ্রেডটিকে 1 সেকেন্ডের জন্য ব্লক করবে। এটি দুর্বল ব্যবহারকারীর অভিজ্ঞতার দিকে পরিচালিত করে।
এর কয়েকটি সমাধান রয়েছে:
-
pthread_detach
-
-s PROXY_TO_PTHREAD
- কাস্টম ওয়ার্কার এবং কমলিংক
pthread_detach
প্রথমত, যদি আপনাকে শুধুমাত্র মূল থ্রেড থেকে কিছু কাজ চালাতে হয়, কিন্তু ফলাফলের জন্য অপেক্ষা করতে না হয়, তাহলে আপনি pthread_join
এর পরিবর্তে pthread_detach
ব্যবহার করতে পারেন। এটি ব্যাকগ্রাউন্ডে চলমান থ্রেড কলব্যাক ছেড়ে দেবে। আপনি যদি এই বিকল্পটি ব্যবহার করেন, তাহলে আপনি -s PTHREAD_POOL_SIZE_STRICT=0
দিয়ে সতর্কতাটি বন্ধ করতে পারেন।
PROXY_TO_PTHREAD
দ্বিতীয়ত, আপনি যদি একটি লাইব্রেরির পরিবর্তে একটি C অ্যাপ্লিকেশন কম্পাইল করেন, আপনি -s PROXY_TO_PTHREAD
বিকল্পটি ব্যবহার করতে পারেন, যা অ্যাপ্লিকেশন দ্বারা তৈরি যেকোনো নেস্টেড থ্রেড ছাড়াও একটি পৃথক থ্রেডে মূল অ্যাপ্লিকেশন কোড অফলোড করবে। এইভাবে, প্রধান কোড যে কোনো সময় UI হিমায়িত না করে নিরাপদে ব্লক করতে পারে। ঘটনাক্রমে, এই বিকল্পটি ব্যবহার করার সময়, আপনাকে থ্রেড পুলটি আগে থেকে তৈরি করতে হবে না - পরিবর্তে, নতুন অন্তর্নিহিত কর্মী তৈরির জন্য এমস্ক্রিপ্টেন মূল থ্রেডটি ব্যবহার করতে পারে এবং তারপর ডেডলকিং ছাড়াই pthread_join
এ হেল্পার থ্রেডটিকে ব্লক করতে পারে।
কমলিঙ্ক
তৃতীয়ত, আপনি যদি একটি লাইব্রেরিতে কাজ করেন এবং এখনও ব্লক করার প্রয়োজন হয়, আপনি আপনার নিজস্ব কর্মী তৈরি করতে পারেন, এমস্ক্রিপ্টেন-জেনারেটেড কোড আমদানি করতে পারেন এবং কমলিঙ্কের সাথে এটিকে মূল থ্রেডে প্রকাশ করতে পারেন। প্রধান থ্রেড অ্যাসিঙ্ক্রোনাস ফাংশন হিসাবে যে কোনও রপ্তানি করা পদ্ধতিগুলিকে আহ্বান করতে সক্ষম হবে এবং সেই উপায়টি UI ব্লক করা এড়াবে।
একটি সাধারণ অ্যাপ্লিকেশনে যেমন পূর্ববর্তী উদাহরণ -s PROXY_TO_PTHREAD
হল সেরা বিকল্প:
emcc -pthread -s PROXY_TO_PTHREAD example.c -o example.js
সি++
সমস্ত একই সতর্কতা এবং যুক্তি একইভাবে C++ এ প্রযোজ্য। আপনি যে নতুন জিনিসটি লাভ করেন তা হল উচ্চ-স্তরের API--এ অ্যাক্সেস যেমন 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.
মরিচা
Emscripten এর বিপরীতে, Rust-এর কোনো বিশেষায়িত এন্ড-টু-এন্ড ওয়েব টার্গেট নেই, কিন্তু এর পরিবর্তে জেনেরিক WebAssembly আউটপুটের জন্য একটি জেনেরিক wasm32-unknown-unknown
টার্গেট প্রদান করে।
যদি Wasm একটি ওয়েব পরিবেশে ব্যবহার করার উদ্দেশ্যে করা হয়, তাহলে JavaScript API-এর সাথে যে কোনো ইন্টারঅ্যাকশন বাহ্যিক লাইব্রেরি এবং টুলিং যেমন wasm-bindgen এবং wasm-pack- এ ছেড়ে দেওয়া হয়। দুর্ভাগ্যবশত, এর মানে হল যে স্ট্যান্ডার্ড লাইব্রেরি ওয়েব ওয়ার্কার্স সম্পর্কে সচেতন নয় এবং স্ট্যান্ডার্ড API যেমন std::thread
WebAssembly-এ কম্পাইল করা হলে কাজ করবে না।
সৌভাগ্যবশত, মাল্টিথ্রেডিংয়ের যত্ন নেওয়ার জন্য বেশিরভাগ ইকোসিস্টেম উচ্চ-স্তরের লাইব্রেরির উপর নির্ভর করে। সেই স্তরে সমস্ত প্ল্যাটফর্মের পার্থক্যগুলিকে বিমূর্ত করা অনেক সহজ।
বিশেষ করে, মরিচা-এ ডেটা-সমান্তরালতার জন্য রেয়ন সবচেয়ে জনপ্রিয় পছন্দ। এটি আপনাকে নিয়মিত পুনরাবৃত্তিকারীতে পদ্ধতির চেইন নিতে দেয় এবং সাধারণত একটি একক লাইন পরিবর্তনের সাথে সেগুলিকে এমনভাবে রূপান্তর করে যেখানে তারা ক্রমানুসারে পরিবর্তে সমস্ত উপলব্ধ থ্রেডে সমান্তরালভাবে চলবে। যেমন:
pub fn sum_of_squares(numbers: &[i32]) -> i32 {
numbers
.iter()
.par_iter()
.map(|x| x * x)
.sum()
}
এই ছোট পরিবর্তনের সাথে, কোডটি ইনপুট ডেটা বিভক্ত করবে, সমান্তরাল থ্রেডে x * x
এবং আংশিক যোগফল গণনা করবে এবং শেষ পর্যন্ত সেই আংশিক ফলাফলগুলি একসাথে যোগ করবে।
std::thread
কাজ না করে প্ল্যাটফর্মের জন্য মিটমাট করার জন্য, রেয়ন এমন হুক সরবরাহ করে যা থ্রেড তৈরি এবং প্রস্থান করার জন্য কাস্টম লজিককে সংজ্ঞায়িত করতে দেয়।
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()
}
একবার হয়ে গেলে, জেনারেট করা জাভাস্ক্রিপ্ট একটি অতিরিক্ত initThreadPool
ফাংশন রপ্তানি করবে। এই ফাংশনটি শ্রমিকদের একটি পুল তৈরি করবে এবং রেয়ন দ্বারা করা যেকোন মাল্টিথ্রেডেড অপারেশনের জন্য প্রোগ্রামের সারাজীবনে তাদের পুনরায় ব্যবহার করবে।
এই পুল মেকানিজমটি এমস্ক্রিপ্টেনের -s PTHREAD_POOL_SIZE=...
বিকল্পের মতো যা আগে ব্যাখ্যা করা হয়েছে, এবং অচলাবস্থা এড়াতে মূল কোডের আগে শুরু করতে হবে:
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
-generated কোড আমদানি করা উচিত, এবং মূল থ্রেডে Comlink-এর মতো একটি লাইব্রেরির সাথে এর API প্রকাশ করা উচিত।
এন্ড-টু-এন্ড ডেমো দেখানোর জন্য wasm-bindgen-rayon উদাহরণ দেখুন:
- থ্রেড বৈশিষ্ট্য সনাক্তকরণ.
- একই মরিচা অ্যাপের একক- এবং বহু-থ্রেডেড সংস্করণ তৈরি করা।
- একজন ওয়ার্কারে wasm-bindgen দ্বারা জেনারেট করা JS+Wasm লোড করা হচ্ছে।
- একটি থ্রেড পুল শুরু করতে wasm-bindgen-rayon ব্যবহার করে।
- Comlink ব্যবহার করে ওয়ার্কার্স এপিআই প্রধান থ্রেডে প্রকাশ করা।
বাস্তব-বিশ্ব ব্যবহারের ক্ষেত্রে
আমরা ক্লায়েন্ট-সাইড ইমেজ কম্প্রেশনের জন্য Squosh.app- এ সক্রিয়ভাবে WebAssembly থ্রেড ব্যবহার করি—বিশেষ করে, AVIF (C++), JPEG-XL (C++), OxiPNG (Rust) এবং WebP v2 (C++) এর মতো ফরম্যাটের জন্য। শুধুমাত্র মাল্টিথ্রেডিংয়ের জন্য ধন্যবাদ, আমরা ধারাবাহিক 1.5x-3x স্পিড-আপ দেখেছি (কোডেক প্রতি সঠিক অনুপাত আলাদা), এবং WebAssembly SIMD- এর সাথে WebAssembly থ্রেডগুলিকে একত্রিত করে সেই সংখ্যাগুলিকে আরও এগিয়ে নিতে সক্ষম হয়েছি!
Google Earth হল আরেকটি উল্লেখযোগ্য পরিষেবা যা তার ওয়েব সংস্করণের জন্য WebAssembly থ্রেড ব্যবহার করছে।
FFMPEG.WASM হল একটি জনপ্রিয় FFmpeg মাল্টিমিডিয়া টুলচেনের একটি WebAssembly সংস্করণ যা সরাসরি ব্রাউজারে ভিডিওগুলিকে দক্ষতার সাথে এনকোড করতে WebAssembly থ্রেড ব্যবহার করে।
সেখানে WebAssembly থ্রেড ব্যবহার করে আরও অনেক উত্তেজনাপূর্ণ উদাহরণ রয়েছে। ডেমোগুলি পরীক্ষা করে দেখুন এবং ওয়েবে আপনার নিজস্ব মাল্টিথ্রেডেড অ্যাপ্লিকেশন এবং লাইব্রেরি আনতে ভুলবেন না!