حسان امینی لو
حسان امینی لو
خواندن ۶ دقیقه·۲ سال پیش

با ‌Worker ها توی JS‌ آشنا بشیم! تو JS هم میشه مالتی-ترد بود

حتما میدونی که جاواسکریپت زبونی هست که Single Thread هست. یعنی در یک زمان فقط یک کار رو میتونه انجام بده. بنابراین برای اجرا شدنش چندین صف مختلف وجود داره که بتونه هم این کار ها رو مدیریت کنه و هم Performance اش رو حفظ کنه.

ولی حتی تو این حالت ممکنه بعضی اوقات بعضی کار ها پیش بیاد که پیش خودتون بگید کاش حالتی بود که میشد Multi Thread هم داشت. خب در حالت عادی قطعا نمیتونید همچین کاری انجام بدید ولی Worker ها بوجود اومدن که دقیقا برای ما این مشکل رو حل کنن. Worker ها به ما این قابلیت رو میدن که بعضی تسک ها رو بفرستید یه جای دیگه محاسبه بشه و صرفا نتیجه اش برگرده سمت اپلیکیشن ما.

به درد کجا میخوره؟ جایی که محاسبات زیاده! جایی که رد و بدل شدن دیتا بین کلاینت و سرور رو نمیخواید خودتون کنترل کنید. هر کاری که دوست دارید میتونید به کمک Worker ها انجام بدید.

بریم ببینیم چطور میشه از Worker ها استفاده کرد و دقیقا به چه دردی میخورن. سعی میکنم مثال هایی که میزنم تو دنیای واقعی هم بشه استفاده کرد.

همینطور که گفتم ما توی جاواسکریپت یک Thread بیشتر نداریم، بنابراین اگر کار سنگینی بخواد انجام بشه که این Thread اصلی رو متوقف کنه عملا صفحه بی استفاده میشه تا اون کار تموم بشه، مگه اینکه اون تسک رو به صورت Async هندل کنید که باز هم اگه کاری که توی اون تسک میخواد انجام بشه سنگین باشه نتیجه خیلی تفاوتی نمیکنه.

توی تصویر بالا همینطور که میبینید نمودار سبز همون Thread اصلی هست. توی جاواسکریپت همه کار هایی که انجام میدیم روی این Thread انجام میشه. به کمک Worker ها میتونیم یک Thread جداگانه ایجاد کنیم و بعضی کار ها رو روی اون انجام بدیم و نتیجش رو روی Thread اصلی بگیریم و باهاش کار کنیم. یکم شبیه تسک های Async میمونه با چندتا تفاوت.


کارگران مشغول کارند

کاری که Worker براتون انجام میده اینه که وقتی ایجادش میکنید یک Thread جدا ساخته میشه که از Thread اصلی جاواسکریپت جداست، برای ساختن یک Worker میتونیم یه دونه فایل بسازیم، مثلا worker.js و بذاریمش هرجا که دوست داریم، بهتره کنار فایل اصلیمون باشه.

توی فایل index.html آدرس میدیم به فایل scripts.js و با worker فعلا کاری نداریم
توی فایل index.html آدرس میدیم به فایل scripts.js و با worker فعلا کاری نداریم


داخل فایل scripts.js برای ایجاد یک worker جدید به این صورت عمل می‌کنیم:

const worker = new Worker('./worker.js'); console.log(worker);

پس بنابراین نیازی نیست که فایل worker رو داخل صفحه html به صورت یه script قرار بدیم. داخل فایل worker میتونیم هرکاری دلمون میخواد انجام بدیم.


ارتباط Thread اصلی و Worker

اگر قبلا تجربه کار کردن با iframe رو داشته باشید احتمالا باید با یک event handler به نام آشنا باشید، و برای ارسال اطلاعات به اون صفحه iframe هم از متد postMessage استفاده می‌کنیم.

دقیقا همون سیستم توی Worker ها هم وجود داره، برای ارسال اطلاعات به فایل worker.js باید توی فایل والد که اینجا همون scripts.js هست از متد postMessage استفاده کنیم و برای دریافت اطلاعات ازش هم از . این ارتباط چون به صورت دو طرفه هندل میشه همین ۲ تا رو توی فایل worker هم داریم.

پس یه چیزی شبیه این میشه:



برای اینکار باید داخل scripts.js همین postMessage و رو داشته باشیم:

const worker = new Worker('./worker.js'); worker.postMessage({ type: 'add', values: [5, 2] }); worker.((e) => { console.log(e.data); });

الان ما یه آبجکت فرستادیم برای worker که داخلش یه type داریم که مقدارش رو add گذاشتیم، بعدا داخل worker میتونیم اینطوری دریافتش کنیم:

const add = (a, b) => a + b; = (e) => { if (e.data.type === 'add') { postMessage(add(...e.data.values)); } }

اینجا ما یه فانکشن داریم به اسم add که فقط ۲ تا عدد میگیره و جمع میکنه و برمیگردونه، توی هم نگاه می‌کنیم که اگر e.data (هر چیری که میفرستید بعنوان data میره داخل این قسمت از event) مقدار type اش add بود ما نتیجه رو محاسبه می‌کنیم و به کمک postMessage نتیجه رو میفرستیم به scripts.js. اونطرف هم که console.log کردیم باید روی کنسول عدد ۷ رو ببینیم.

تمام.


استفاده از worker ها به همین سادگیه، ولی چندتا محدودیت و نکته مهم داره که باید بدونیم:

  1. توی فایل worker ما دسترسی به global آبجکت نداریم، مثلا اینجا دسترسی به window وجود نداره.
  2. توی worker ها از یک global object دیگه استفاده میکنه سوا از window که اسمش هست: DedicatedWorkerGlobalScope.
  3. شما توی فایل های worker دسترسی به DOM ندارید و نمیشه ازش برای manipulate کردن DOM استفاده کرد.
  4. از worker ها باید با احتیاط استفاده کرد، برای هرکاری نباید زرتی یه دونه worker درست کنید.
  5. بهتره برای محاسبه های سنگین که ممکنه cpu رو خیلی درگیر کنه، برای ارتباط های real-time یا برای برقراری ارتباط socket ازش استفاده کرد و عموما برای همین موارد هم ازش استفاده می‌شه.
  6. یه کلمه کلیدی داخل فایل worker وجود داره به اسم self که ممکنه بعضی جاها ببینید ازش استفاده شده به این شکل: self. و self.postMessage مثل همون window میمونه توی فایل scripts.js و اشاره میکنه به همون DedicatedWorkerGlobalScope. اگه ننویسیدش هم مشکلی پیش نمیاد، مگر اینکه کار خاص تری بخواید انجام بدید که بهش نیاز داشته باشید.


میتونیم چندتا worker همزمان داشته باشیم

میتونید به هر تعداد که دلتون میخواد worker ایجاد کنید و با هرکدومش یه کار انجام بدید، مثلا یکی باشه برای ارتباط socket، یکی دیگه باشه برای محاسبات سنگین و یکی دیگه هم بذارید برای قشنگی! نکته اینه که تا جایی که من میدونم محدودیت خاصی وجود نداره، ولی نکته ای که هست اینه که اون worker فقط توی فایلی که ساخته شده قابل دسترس هست.

برای حل این موضوع میتونید از نوع دیگری از worker ها استفاده کنید که همه چیزشون با worker های معمولی یکیه با این تفاوت که میتونید بین چندتا فایل به اشتراک گذاشته بشن، اسمشون هم هست SharedWorker. این worker ها بر خلاف ورکر های قبلی میتونن بین صفحات مختلف، بین فایل های مختلف و حتی بین worker های مختلف قابل دسترس باشن. چون خیلی شبیه worker ها هستن من دیگه اشاره ای به نحوه استفاده ازشون نمیکنم و پیشنهاد میدم این لینک رو برید بخونید.


تفاوت worker ها با ServiceWorker ها

احتمالا قبلا اسم ServiceWorker رو شنیده باشید و حتی استفاده کرده باشید ازشون، برای cache کردن اطلاعات و عموما برای ساختن PWA ها ازشون استفاده میشه، برای اینکه مفاهیم Web Worker ها با Service Worker ها قاطی نشه لازم دونستم که به تفاوت هاش اشاره کنم:

  1. از worker ها معمولا برای کار های parallel استفاده میشه ولی از ServiceWorker ها برای پشتیبانی از حالت آفلاین اپلیکیشن ها.
  2. میتونید داخل یه تب مرورگر هر تعداد که مایلید worker داشته باشید، ولی ServiceWorker ها یک دونه هستن برای همه تب های اپلیکیشن تون.
  3. وقتی Worker ها رو ایجاد میکنید تا زمانی زنده هستند که اون Tab مرورگر باز باشه، ولی life span توی Service Worker ها به صورت مستقل کنترل میشه.


یه نمونه واقعی تر ببینیم

همونطور که تا اینجا گفتم از Worker ها برای کار های Parallel استفاده میشه، یک تسک خیلی سخت رو میخوام بهتون معرفی کنم:

let total = 0; for (let i = 0; i <= 10000000000; i += 1) { total += i; } console.log(total); console.log('MIAD');

اگر این کد رو روی مرورگرتون اجرا کنید (بسته به سیستم تون) ممکنه بین ۵ تا ۳۰ ثانیه جوابش طول بکشه و بعد از اینکه جوابشم آماده شد تازه اون خط آخر که MIAD رو چاپ میکنه. پس عملا تا زمانی که کار این حلقه تموم نشده اجرای باقی کد متوقف میشه. اینجاس که میتونیم از worker برای هندل کردن اینکار استفاده کنیم پس این تسک رو می‌بریم توی worker:

worker.js

const calc = () => { let total = 0; for (let i = 0; i <= 10000000000; i += 1) { total += i; } return total; }; = (e) => { if (e.data.type === 'calc') { postMessage(calc()); } }

scripts.js

const worker = new Worker('./worker.js'); console.log('1'); worker.postMessage({ type: 'calc' }); worker. = (e) => console.log(e.data); console.log('2');

با این شرایط اگر این کد رو اجرا کنید روی کنسول همچین چیزی میبینید:

بعد از چند ثانیه نتیجه رو میبینیم.
بعد از چند ثانیه نتیجه رو میبینیم.


این تسک روی یک Thread دیگه انجام شد و خللی تو اجرای Main Thread ایجاد نشد.


مثال از Worker ها و Socket

این مثال به کمک React و Worker ها انجام شده، فایل های زیاده داره، مثال رو روی گیت قرار دادم که میتونید از اونجا ببینیدش، پیشنهاد میکنم حتما حتما مثال رو ببینید چون علاوه بر کاربرد Worker میتونید ببینید چطوری داخل React ازش میشه استفاده کرد.

https://github.com/hesan-aminiloo/react-sample-workers



دمتون گرم که تا اینجای مطلب رو خوندید، امیدوارم که بتونید به کمک Worker ها کارای خفن تر انجام بدید! اگه دوست داشتید میتونید از طریق لینکدین باهام در ارتباط باشید.




javascriptjavascript workersآموزش javascriptحسانmultithread programming
برنامه نویس از جلو
شاید از این پست‌ها خوشتان بیاید