اختار المؤلف فتح المصادر للأمراض العقلية ليتلقى تبرعًا كجزء من برنامج اكتب من أجل التبرعات.
المقدمة
Node.js يقوم بتشغيل كود JavaScript في خيط واحد، وهذا يعني أن كودك يمكن أن يقوم بمهمة واحدة في كل مرة. ومع ذلك، فإن Node.js نفسه متعدد الخيوط ويوفر خيوطًا مخفية من خلال مكتبة libuv
، التي تتعامل مع عمليات الإدخال/الإخراج مثل قراءة الملفات من القرص أو طلبات الشبكة. من خلال استخدام الخيوط المخفية، يوفر Node.js أساليب غير متزامنة تسمح لكودك بإجراء طلبات إدخال/إخراج دون حظر الخيط الرئيسي.
على الرغم من وجود خيوط مخفية في Node.js، لا يمكنك استخدامها لتفريغ المهام المكثفة لوحدة المعالجة المركزية، مثل الحسابات المعقدة أو تغيير حجم الصور أو ضغط الفيديو. نظرًا لأن JavaScript لا يزال أحادي الخيط عند تشغيل مهمة مكثفة لوحدة المعالجة المركزية، يحجب الخيط الرئيسي ولا يتم تنفيذ أي كود آخر حتى اكتمال المهمة. بدون استخدام خيوط أخرى، الطريقة الوحيدة لتسريع مهمة مرتبطة بوحدة المعالجة المركزية هي زيادة سرعة المعالج.
ومع ذلك، في السنوات الأخيرة، لم تكن وحدات المعالجة المركزية تحصل على سرعة أعلى. بدلاً من ذلك، يتم شحن الحواسيب مع مزيد من النوى، وأصبح من الأكثر شيوعاً الآن أن تحتوي الحواسيب على 8 أو أكثر من النوى. وعلى الرغم من هذه الاتجاهات، فإن كودك لن يستفيد من النوى الإضافية على حاسوبك لتسريع المهام المرتبطة بوحدة المعالجة المركزية أو تجنب كسر الخيط الرئيسي لأن جافا سكريبت تعمل بخيط واحد.
لتصحيح هذا، قدمت Node.js وحدة worker-threads
التي تسمح لك بإنشاء خيوط وتنفيذ مهام جافا سكريبت متعددة بشكل متواز. بمجرد أن تنتهي خيط من مهمة، يرسل رسالة إلى الخيط الرئيسي تحتوي على نتيجة العملية بحيث يمكن استخدامها مع أجزاء أخرى من الكود. ميزة استخدام خيوط العمل هي أن المهام المرتبطة بوحدة المعالجة المركزية لا تحجب الخيط الرئيسي ويمكنك تقسيم المهمة وتوزيعها على عدة عمال لتحسينها.
في هذا البرنامج التعليمي، ستقوم بإنشاء تطبيق Node.js مع مهمة مكثفة لوحدة المعالجة المركزية تحجب الخيط الرئيسي. بعد ذلك، ستستخدم وحدة worker-threads
لنقل المهمة المكثفة لوحدة المعالجة المركزية إلى خيط آخر لتجنب حجب الخيط الرئيسي. وأخيراً، ستقوم بتقسيم المهمة المرتبطة بوحدة المعالجة المركزية وستجعل أربعة خيوط تعمل عليها بشكل متواز لتسريع المهمة.
المتطلبات المسبقة
لإكمال هذا البرنامج التعليمي، ستحتاج:
-
نظام متعدد النوى بأربعة أو أكثر من النوى. يمكنك متابعة البرنامج التعليمي من الخطوات 1 إلى 6 على نظام ثنائي النواة. ومع ذلك، يتطلب الخطوة 7 أربعة أنوية لرؤية تحسينات الأداء.
-
بيئة تطوير Node.js. إذا كنت على أوبونتو 22.04، قم بتثبيت الإصدار الأخير من Node.js عن طريق اتباع الخطوة 3 من كيفية تثبيت Node.js على أوبونتو 22.04. إذا كنت على نظام تشغيل آخر، انظر كيفية تثبيت Node.js وإنشاء بيئة تطوير محلية.
-
فهم جيد لحلقة الأحداث، ودعوات العودة، والوعود في JavaScript، والتي يمكنك العثور عليها في دليلنا، فهم حلقة الأحداث، ودعوات العودة، والوعود، وAsync/Await في JavaScript.
-
معرفة أساسية عن كيفية استخدام إطار العمل الويب Express. تحقق من دليلنا، كيفية البدء مع Node.js و Express.
إعداد المشروع وتثبيت الاعتماديات
في هذه الخطوة، ستقوم بإنشاء دليل المشروع، وتهيئة npm، وتثبيت جميع الاعتماديات اللازمة.
للبدء، أنشئ وانتقل إلى دليل المشروع:
- mkdir multi-threading_demo
- cd multi-threading_demo
يُنشئ الأمر mkdir دليلاً ويغير الأمر cd الدليل العامل إلى الذي تم إنشاؤه حديثًا.
بعد ذلك، قم بتهيئة دليل المشروع باستخدام npm باستخدام الأمر npm init:
- npm init -y
الخيار -y يقبل جميع الخيارات الافتراضية.
عند تشغيل الأمر، سيبدو مخرجاتك مشابهة لهذا:
Wrote to /home/sammy/multi-threading_demo/package.json:
{
"name": "multi-threading_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
بعد ذلك، قم بتثبيت express
، إطار عمل Node.js:
- npm install express
ستستخدم Express لإنشاء تطبيق خادم يحتوي على نقاط نهاية محظورة وغير محظورة.
يأتي Node.js مع وحدة worker-threads
افتراضيًا، لذا لا داعي لتثبيتها.
لقد قمت الآن بتثبيت الحزم اللازمة. في الخطوة التالية، ستتعرف على المزيد حول العمليات والخيوط وكيفية تنفيذها على الكمبيوتر.
فهم العمليات والمواضيع
قبل أن تبدأ في كتابة المهام المرتبطة بوحدة المعالجة المركزية وإلغائها لمواضيع منفصلة ، تحتاج أولاً إلى فهم ما هي العمليات والمواضيع ، والاختلافات بينهما. الأهم من ذلك ، ستستعرض كيفية تنفيذ العمليات والمواضيع على نظام كمبيوتر مفرد أو متعدد النوى.
العملية
A process is a running program in the operating system. It has its own memory and cannot see nor access the memory of other running programs. It also has an instruction pointer, which indicates the instruction currently being executed in a program. Only one task can be executed at a time.
لفهم ذلك ، ستقوم بإنشاء برنامج Node.js يحتوي على حلقة لا نهائية بحيث لا ينتهي عند تشغيله.
باستخدام nano
، أو محرر النص الخاص بك المفضل ، أنشئ وافتح ملف process.js
:
- nano process.js
في ملف process.js
الخاص بك ، أدخل الشيفرة التالية:
const process_name = process.argv.slice(2)[0];
count = 0;
while (true) {
count++;
if (count == 2000 || count == 4000) {
console.log(`${process_name}: ${count}`);
}
}
في السطر الأول ، تعيد خاصية process.argv
مصفوفة تحتوي على وسائط سطر الأوامر للبرنامج. ثم ترفق طريقة slice()
لجافا سكريبت مع وسيط يساوي 2
لإنشاء نسخة ضحلة من المصفوفة من الفهرس 2 فصاعدًا. يتم بذلك تجاوز الوسيطين الأولين ، وهما مسار Node.js واسم الملف التنفيذي. بعد ذلك ، تستخدم بناء الجملة بالأقواس المربعة لاسترداد الوسيط الأول من المصفوفة المقطوعة وتخزينه في المتغير process_name
.
بعد ذلك، يتم تعريف حلقة `while` وتمرير شرط `true` لتشغيل الحلقة بشكل دائم. داخل الحلقة، يتم زيادة متغير `count` بمقدار `1` خلال كل تكرار. بعد ذلك يأتي عبارة `if` التي تفحص ما إذا كانت قيمة `count` تساوي `2000` أو `4000`. إذا كان الشرط صحيحًا، فإن أسلوب `console.log()` يسجل رسالة في المحطة.
احفظ وأغلق ملفك باستخدام CTRL+X
، ثم اضغط Y
لحفظ التغييرات.
قم بتشغيل البرنامج باستخدام أمر node
:
- node process.js A &
A
is a command-line argument that is passed to the program and stored in the process_name
variable. The &
at end the allows the Node program to run in the background, which lets you enter more commands in the shell.
عند تشغيل البرنامج، سترى إخراجًا مشابهًا لما يلي:
Output[1] 7754
A: 2000
A: 4000
الرقم 7754
هو معرف عملية تعيينه النظام التشغيل له. A: 2000
و A: 4000
هما إخراج البرنامج.
عند تشغيل برنامج باستخدام أمر node
، فإنك تنشئ عملية. يخصص النظام التشغيل ذاكرة للبرنامج، ويحدد موقع البرنامج التنفيذي على قرص الكمبيوتر الخاص بك، ويحمل البرنامج في الذاكرة. ثم يعين له معرف عملية ويبدأ في تنفيذ البرنامج. في هذا النقطة، أصبح برنامجك الآن عملية.
عند تشغيل العملية، يتم إضافة معرف العملية إلى قائمة العمليات في نظام التشغيل ويمكن رؤيته باستخدام أدوات مثل htop
, top
, أو ps
. توفر هذه الأدوات تفاصيل أكثر حول العمليات، وكذلك خيارات لإيقافها أو إعطائها الأولوية.
للحصول على ملخص سريع لعملية Node، اضغط ENTER
في الطرفية الخاصة بك للحصول على الومضة مرة أخرى. بعد ذلك، قم بتشغيل الأمر ps
لرؤية عمليات Node:
- ps |grep node
الأمر ps
يعرض قائمة بجميع العمليات المرتبطة بالمستخدم الحالي على النظام. يستخدم عامل الأنابيب |
لتمرير جميع إخراج ps
إلى grep
لتصفية العمليات لعرض عمليات Node فقط.
تشغيل الأمر سينتج إخراجًا مشابهًا لما يلي:
Output7754 pts/0 00:21:49 node
يمكنك إنشاء عمليات لا تحصى من برنامج واحد. على سبيل المثال، استخدم الأمر التالي لإنشاء ثلاث عمليات إضافية بمعلمات مختلفة ووضعها في الخلفية:
- node process.js B & node process.js C & node process.js D &
في الأمر، قمت بإنشاء ثلاث حالات أخرى من برنامج process.js
. الرمز &
يضع كل عملية في الخلفية.
عند تشغيل الأمر، سيبدو الإخراج مشابهًا لما يلي (على الرغم من أن الترتيب قد يختلف):
Output[2] 7821
[3] 7822
[4] 7823
D: 2000
D: 4000
B: 2000
B: 4000
C: 2000
C: 4000
كما ترى في الإخراج، سجلت كل عملية اسم العملية في الطرفية عندما وصل العدد إلى 2000
و 4000
. لا تدرك كل عملية أي عملية أخرى تعمل: العملية D
لا تدرك العملية C
، والعكس صحيح. أي شيء يحدث في أي عملية لن يؤثر على عمليات Node.js الأخرى.
أذا كنت تفحص الإخراج عن كثب، سترى أن ترتيب الإخراج ليس هو نفس الترتيب الذي كان عليه عند إنشاء العمليات الثلاث. عند تشغيل الأمر، كانت وسائط العمليات مرتبة على التوالي B
، C
، و D
. ولكن الآن، النظام الأساسي قد قام بتغيير ترتيبها إلى D
، B
، و C
. السبب هو أن نظام التشغيل يحتوي على خوارزميات جدولة تقرر أي عملية يتم تشغيلها على وحدة المعالجة المركزية في وقت محدد.
على جهاز يحتوي على نواة واحدة، تقوم العمليات بالتنفيذ بشكل متزامن. وهذا يعني أن نظام التشغيل يتنقل بين العمليات بفواصل زمنية منتظمة. على سبيل المثال، تنفذ العملية D
لفترة زمنية محدودة، ثم يتم حفظ حالتها في مكان ما ويقوم نظام التشغيل بجدولة تنفيذ العملية B
لفترة زمنية محدودة، وهكذا. يحدث هذا ذهابًا وإيابًا حتى تنتهي جميع المهام. من الإخراج، قد يبدو أن كل عملية قد قدمت إلى النهاية، ولكن في الواقع، جدول تشغيل نظام التشغيل يقوم بالتبديل باستمرار بينها.
على جهاز يحتوي على عدة نوى – بفرضك أن لديك أربعة نوى – يقوم نظام التشغيل بجدولة كل عملية لتنفيذها على كل نواة في نفس الوقت. يُعرف هذا باسم التوازي. ومع ذلك، إذا قمت بإنشاء أربع عمليات إضافية (مما يرفع الإجمالي إلى ثمانية)، ستقوم كل نواة بتنفيذ عمليتين بشكل متزامن حتى انتهاءهما.
الخيوط
المواضيع شبيهة بالعمليات: لديها مؤشر تعليمات خاص بها ويمكنها تنفيذ مهمة JavaScript واحدة في كل مرة. على عكس العمليات، المواضيع ليس لديها ذاكرة خاصة بها. بدلاً من ذلك، تتواجد ضمن ذاكرة العملية. عند إنشاء عملية، يمكن أن تحتوي على عدة مواضيع تم إنشاؤها باستخدام وحدة worker_threads
لتنفيذ كود JavaScript بشكل متوازٍ. علاوة على ذلك، يمكن للمواضيع التواصل مع بعضها البعض من خلال تبادل الرسائل أو مشاركة البيانات في ذاكرة العملية. هذا يجعلها خفيفة الوزن مقارنة بالعمليات، حيث أن إنشاء موضوع لا يتطلب المزيد من الذاكرة من نظام التشغيل.
عندما يتعلق الأمر بتنفيذ المواضيع، لديها سلوك مماثل لتلك التي في العمليات. إذا كان لديك عدة مواضيع تعمل على نظام ذو نواة واحدة، سيقوم نظام التشغيل بالتبديل بينها في فترات منتظمة، مما يمنح كل موضوع فرصة للتنفيذ مباشرة على وحدة المعالجة المركزية الواحدة. على نظام ذو عدة نوى، يقوم نظام التشغيل بجدولة المواضيع عبر جميع النوى وتنفيذ كود JavaScript في نفس الوقت. إذا انتهيت من إنشاء المزيد من المواضيع مما يتوفر من النوى، ستقوم كل وحدة معالجة بتنفيذ عدة مواضيع بشكل متزامن.
مع ذلك، اضغط على ENTER
ثم قم بإيقاف جميع عمليات Node التي تعمل حاليًا باستخدام أمر kill
:
- sudo kill -9 `pgrep node`
pgrep
يعيد معرفات العمليات لكل من عمليات Node الأربع إلى أمر kill
. الخيار -9
يوجه kill
لإرسال إشارة SIGKILL.
عند تشغيل الأمر، سترى مخرجات مشابهة لما يلي:
Output[1] Killed node process.js A
[2] Killed node process.js B
[3] Killed node process.js C
[4] Killed node process.js D
لأحيانٍ قد تتأخر النتيجة وتظهر عند تشغيل أمر آخر.
الآن بعد أن تعرف الفرق بين العملية والخيط، ستتعامل مع الخيوط الخفية في Node.js في القسم التالي.
فهم الخيوط الخفية في Node.js
Node.js يوفر خيوطًا إضافية، ولهذا يُعتبر متعدد الخيوط. في هذا القسم، ستستعرض الخيوط الخفية في Node.js التي تساعد في جعل عمليات الإدخال/الإخراج غير مُحجوبة.
كما هو مذكور في المقدمة، الجافا سكريبت هي أحادية الخيط، ويتم تنفيذ جميع أكواد الجافا سكريبت في خيط واحد. وهذا يشمل شيفرة المصدر لبرنامجك والمكتبات الخارجية التي تضمنها في برنامجك. عندما يقوم البرنامج بعملية إدخال/إخراج لقراءة ملف أو طلب شبكي، يتسبب ذلك في تعليق الخيط الرئيسي.
ومع ذلك، ينفذ Node.js مكتبة libuv
، التي توفر أربع خيوط إضافية لعملية Node.js. باستخدام هذه الخيوط، يتم التعامل مع عمليات الإدخال/الإخراج بشكل منفصل، وعند انتهائها، يقوم حلقة الأحداث بإضافة الاستدعاء المرتبط بمهمة الإدخال/الإخراج في طابور مهام صغير. عندما يكون الكومة الاستدعائية في الخيط الرئيسي واضحة، يتم وضع الاستدعاء في الكومة الاستدعائية ومن ثم تنفيذه. لتوضيح الأمر، فإن الاستدعاء المرتبط بمهمة الإدخال/الإخراج المعطاة لا يتم تنفيذه بشكل متوازي؛ ومع ذلك، فإن المهمة نفسها لقراءة ملف أو طلب شبكة تحدث بشكل متوازي بمساعدة الخيوط. بمجرد انتهاء مهمة الإدخال/الإخراج، يتم تشغيل الاستدعاء في الخيط الرئيسي.
بالإضافة إلى هذه الخيوط الأربعة، توفر محرك V8 أيضًا خيطين للتعامل مع أشياء مثل جمع القمامة تلقائيًا. وهذا يجلب إجمالي عدد الخيوط في العملية إلى سبعة: خيط رئيسي واحد، وأربعة خيوط Node.js، وخيطين V8.
للتحقق من أن كل عملية Node.js تحتوي على سبعة خيوط، قم بتشغيل ملف process.js
مرة أخرى وضعه في الخلفية:
- node process.js A &
سيقوم محط الطرفية بتسجيل معرف العملية، بالإضافة إلى الإخراج من البرنامج:
Output[1] 9933
A: 2000
A: 4000
ادرج معرف العملية في مكان ما واضغط على ENTER
حتى تتمكن من استخدام الواجهة مرة أخرى.
لرؤية الخيوط، قم بتشغيل الأمر top
وقم بتمرير معرف العملية المعروض في الإخراج:
- top -H -p 9933
يُشير -H
إلى top
لعرض الخيوط في عملية. يُشير العلم -p
إلى top
لمراقبة النشاط فقط في معرف العملية المعطى.
عند تشغيل الأمر، ستبدو النتيجة الخاصة بك مشابهة لما يلي:
Outputtop - 09:21:11 up 15:00, 1 user, load average: 0.99, 0.60, 0.26
Threads: 7 total, 1 running, 6 sleeping, 0 stopped, 0 zombie
%Cpu(s): 24.8 us, 0.3 sy, 0.0 ni, 75.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 7951.2 total, 6756.1 free, 248.4 used, 946.7 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 7457.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9933 node-us+ 20 0 597936 51864 33956 R 99.9 0.6 4:19.64 node
9934 node-us+ 20 0 597936 51864 33956 S 0.0 0.6 0:00.00 node
9935 node-us+ 20 0 597936 51864 33956 S 0.0 0.6 0:00.84 node
9936 node-us+ 20 0 597936 51864 33956 S 0.0 0.6 0:00.83 node
9937 node-us+ 20 0 597936 51864 33956 S 0.0 0.6 0:00.93 node
9938 node-us+ 20 0 597936 51864 33956 S 0.0 0.6 0:00.83 node
9939 node-us+ 20 0 597936 51864 33956 S 0.0 0.6 0:00.00 node
كما يمكنك رؤية في الناتج، يحتوي عملية Node.js على سبعة خيوط بمجموع: خيط رئيسي واحد لتنفيذ الجافا سكريبت، وأربعة خيوط Node.js، واثنين من خيوط V8.
كما تم مناقشته سابقًا، تُستخدم الخيوط الأربعة في Node.js لعمليات الإدخال/الإخراج لجعلها غير تزامنية. تعمل بشكل جيد لهذه المهمة، وإنشاء خيوط بشكل يدوي لعمليات الإدخال/الإخراج قد يؤدي حتى إلى تدهور أداء التطبيق. الأمر لا ينطبق بنفس الطريقة على المهام المرتبطة بوحدة المعالج المركزي. فإن مهمة ترتبط بوحدة المعالج المركزي لا تستفيد من أي خيوط إضافية متاحة في العملية وتحجب الخيط الرئيسي.
الآن اضغط على q
للخروج من top
وقم بإيقاف عملية Node بالأمر التالي:
- kill -9 9933
الآن بعد أن تعرف عن الخيوط في عملية Node.js، ستقوم بكتابة مهمة مرتبطة بوحدة المعالج المركزي في القسم التالي وسترى كيف تؤثر على الخيط الرئيسي.
إنشاء مهمة مرتبطة بوحدة المعالج المركزي بدون استخدام خيوط العامل
في هذا القسم، ستقوم ببناء تطبيق Express يحتوي على مسار غير تزامني ومسار متزامن يقوم بتشغيل مهمة مرتبطة بوحدة المعالج المركزي.
أولاً، افتح ملف index.js
في محرر النصوص المفضل لديك:
- nano index.js
في ملف index.js
الخاص بك، أضف الكود التالي لإنشاء خادم أساسي:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/non-blocking/", (req, res) => {
res.status(200).send("This page is non-blocking");
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
في كتلة الشيفرة التالية، تقوم بإنشاء خادم HTTP باستخدام Express. في السطر الأول، تقوم باستيراد وحدة express
. بعد ذلك، تعين المتغير app
لاحتواء نسخة من Express. بعد ذلك، تقوم بتعريف المتغير port
الذي يحمل رقم المنفذ الذي يجب أن يستمع عليه الخادم.
بعد ذلك، تستخدم app.get('/non-blocking')
لتعريف المسار الذي يجب إرسال طلبات GET
إليه. وأخيرًا، تستدعي طريقة app.listen()
لتوجيه الخادم للبدء في الاستماع على المنفذ 3000
.
بعد ذلك، قم بتعريف مسار آخر، /blocking/
، الذي سيحتوي على مهمة مكثفة لوحدة المعالجة المركزية:
...
app.get("/blocking", async (req, res) => {
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
res.status(200).send(`result is ${counter}`);
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
تعرف مسار /blocking
باستخدام app.get("/blocking")
، الذي يأخذ استدعاءً واجبًا غير متزامنًا مسبوقًا بالكلمة الرئيسية async
كوسيط ثانوي يشغل مهمة مكثفة لوحدة المعالجة المركزية. ضمن الاستدعاء، تقوم بإنشاء حلقة for
تكرر 20 مليار مرة وخلال كل تكرار، تزيد قيمة المتغير counter
بمقدار 1
. هذه المهمة تعمل على وحدة المعالجة المركزية وستستغرق بضع ثوانٍ للاكتمال.
في هذه النقطة، سيظهر ملف index.js
الخاص بك بهذا الشكل:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/non-blocking/", (req, res) => {
res.status(200).send("This page is non-blocking");
});
app.get("/blocking", async (req, res) => {
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
res.status(200).send(`result is ${counter}`);
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
احفظ وأغلق ملفك، ثم قم ببدء الخادم باستخدام الأمر التالي:
- node index.js
عند تشغيل الأمر، سترى إخراجًا مماثلًا للتالي:
OutputApp listening on port 3000
هذا يظهر أن الخادم يعمل وجاهز للخدمة.
الآن، قم بزيارة http://localhost:3000/non-blocking
في متصفحك المفضل. سترى استجابة فورية مع الرسالة This page is non-blocking
.
ملحوظة: إذا كنت تتابع البرنامج التعليمي على خادم عن بُعد، يمكنك استخدام توجيه المنفذ لاختبار التطبيق في المتصفح.
بينما يعمل خادم Express، افتح نافذة أخرى في جهاز الكمبيوتر المحلي الخاص بك وأدخل الأمر التالي:
- ssh -L 3000:localhost:3000 your-non-root-user@yourserver-ip
عند الاتصال بالخادم، انتقل إلى http://localhost:3000/non-blocking
على متصفح الويب في جهاز الكمبيوتر المحلي. احتفظ بالنافذة الثانية مفتوحة طوال باقي هذا البرنامج التعليمي.
بعد ذلك، افتح علامة تبويب جديدة وقم بزيارة http://localhost:3000/blocking
. أثناء تحميل الصفحة، قم بفتح علامتي تبويب إضافيتين وقم بزيارة http://localhost:3000/non-blocking
مرة أخرى. سترى أنك لن تحصل على استجابة فورية، وستظل الصفحات تحاول التحميل. إلا بعد أن ينتهي تحميل المسار /blocking
ويعيد الاستجابة النتيجة هي 20000000000
، ستعيد باقي المسارات الاستجابة.
السبب في عدم عمل جميع مسارات /non-blocking
أثناء تحميل مسار /blocking
يعود إلى حلقة for
المرتبطة بوحدة المعالجة المركزية، والتي تعيق الخيط الرئيسي. عندما يتم إعاقة الخيط الرئيسي، لا يمكن لـ Node.js خدمة أي طلب حتى تنتهي مهمة وحدة المعالجة المركزية. لذلك، إذا كانت لديك آلاف من طلبات GET
المتزامنة إلى مسار /non-blocking
، يكفي زيارة واحدة إلى مسار /blocking
لجعل جميع مسارات التطبيق غير قابلة للاستجابة.
كما يمكنك رؤية، يمكن أن يؤدي حجب الخط الرئيسي إلى إضرار تجربة المستخدم مع تطبيقك. لحل هذه المشكلة، ستحتاج إلى تحميل مهمة مرتبطة بوحدة المعالجة المركزية على خط أخر حتى يتمكن الخط الرئيسي من الاستمرار في التعامل مع طلبات HTTP الأخرى.
بعد ذلك، قم بإيقاف تشغيل الخادم عن طريق الضغط على CTRL+C
. ستقوم بتشغيل الخادم مرة أخرى في الجزء التالي بعد إجراء المزيد من التغييرات على ملف index.js
. السبب في توقف الخادم هو أن Node.js لا يقوم بتحديث تلقائي عند إجراء تغييرات جديدة على الملف.
الآن بعد أن فهمت التأثير السلبي الذي يمكن أن تكون لمهمة مكثفة لوحدة المعالجة المركزية على تطبيقك، ستحاول الآن تجنب حجب الخط الرئيسي عن طريق استخدام الوعود.
تحميل مهمة مرتبطة بوحدة المعالجة المركزية باستخدام الوعود
غالبًا ما يلجأ المطورون إلى الوعود عندما يتعلمون عن التأثير الحجبي للمهام المكثفة لوحدة المعالجة المركزية لجعل الشيفرة غير الحجبية. ينبع هذا الغريزة من المعرفة في استخدام طرق الإدخال/الإخراج غير الحجبية القائمة على الوعود، مثل readFile()
و writeFile()
. ولكن كما تعلمت، فإن عمليات الإدخال/الإخراج تستخدم خطوط العمل الخفية في Node.js، والتي لا تتوفر للمهام المكثفة لوحدة المعالجة المركزية. ومع ذلك، في هذا القسم، ستقوم بلف المهمة المكثفة لوحدة المعالجة المركزية في وعد في محاولة لجعلها غير حجبية. لن ينجح هذا الأمر، ولكنه سيساعدك في رؤية قيمة استخدام خطوط العمل، والتي ستقوم بذلك في القسم التالي.
افتح ملف index.js
مرة أخرى في محرر النصوص الخاص بك:
- nano index.js
في ملف الـ index.js
، قم بإزالة الكود المظلل الذي يحتوي على المهمة المكلفة لوحدة المعالجة المركزية:
...
app.get("/blocking", async (req, res) => {
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
res.status(200).send(`result is ${counter}`);
});
...
بعد ذلك، قم بإضافة الكود المظلل التالي الذي يحتوي على وظيفة تقوم بإرجاع وعد:
...
function calculateCount() {
return new Promise((resolve, reject) => {
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
resolve(counter);
});
}
app.get("/blocking", async (req, res) => {
res.status(200).send(`result is ${counter}`);
}
تحتوي وظيفة calculateCount()
الآن على الحسابات التي كانت في وظيفة المعالجة /blocking
. تقوم الوظيفة بإرجاع وعد، الذي يتم تهيئته باستخدام بنية new Promise
. يأخذ الوعد وظيفة رد الاستدعاء مع معلمات resolve
و reject
، التي تتعامل مع النجاح أو الفشل. عندما ينتهي الحلقة for
من التشغيل، يقوم الوعد بالتحقق من القيمة في متغير counter
.
بعد ذلك، قم باستدعاء وظيفة calculateCount()
في وظيفة المعالجة /blocking/
في ملف الـ index.js
:
app.get("/blocking", async (req, res) => {
const counter = await calculateCount();
res.status(200).send(`result is ${counter}`);
});
هنا تقوم باستدعاء وظيفة calculateCount()
مع الكلمة الرئيسية await
مقدمة لانتظار تحقق الوعد. بمجرد أن يتم تحقق الوعد، يتم تعيين المتغير counter
بالقيمة المحددة.
سيبدو كودك الكامل الآن على النحو التالي:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/non-blocking/", (req, res) => {
res.status(200).send("This page is non-blocking");
});
function calculateCount() {
return new Promise((resolve, reject) => {
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
resolve(counter);
});
}
app.get("/blocking", async (req, res) => {
const counter = await calculateCount();
res.status(200).send(`result is ${counter}`);
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
قم بحفظ ملفك وخروجه، ثم قم ببدء الخادم مرة أخرى:
- node index.js
في متصفح الويب الخاص بك، قم بزيارة http://localhost:3000/blocking
وأثناء تحميله، قم بإعادة تحميل علامات التبويب http://localhost:3000/non-blocking
بسرعة. كما ستلاحظ، فإن مسارات non-blocking
لا تزال متأثرة وسينتظر كل منها حتى تنتهي مسارات /blocking
من التحميل. لأن المسارات لا تزال متأثرة، فإن الوعود لا تجعل كود JavaScript يعمل بشكل متوازي ولا يمكن استخدامها لجعل المهام مرتبطة بالمعالجة المركزية غير متأثرة.
بعد ذلك، قم بإيقاف خادم التطبيق باستخدام CTRL+C
.
الآن بعد أن تعلمت أن الوعود لا توفر أي آلية لجعل المهام التي تعتمد على وحدة المعالجة المركزية غير مانعة للتوقف، ستستخدم وحدة worker-threads
في Node.js لنقل مهمة تعتمد على وحدة المعالجة المركزية إلى خيط منفصل.
تحميل مهمة تعتمد على وحدة المعالجة المركزية باستخدام وحدة worker-threads
في هذا القسم، ستقوم بنقل مهمة مكثفة على وحدة المعالجة المركزية إلى خيط آخر باستخدام وحدة worker-threads
لتجنب توقف الخيط الرئيسي. للقيام بذلك، ستقوم بإنشاء ملف worker.js
الذي سيحتوي على المهمة المكثفة على وحدة المعالجة المركزية. في ملف index.js
، ستستخدم وحدة worker-threads
لتهيئة الخيط وبدء المهمة في ملف worker.js
لتشغيلها بشكل موازي مع الخيط الرئيسي. بمجرد اكتمال المهمة، سيقوم الخيط العامل بإرسال رسالة تحتوي على النتيجة إلى الخيط الرئيسي.
للبدء، تحقق من وجود 2 أو أكثر من النوى باستخدام الأمر nproc
:
- nproc
Output4
إذا كان يظهر نواتين أو أكثر، يمكنك المتابعة مع هذه الخطوة.
بعد ذلك، قم بإنشاء وفتح ملف worker.js
في محرر النصوص:
- nano worker.js
في ملف worker.js
، أضف الكود التالي لاستيراد وحدة worker-threads
وتنفيذ المهمة المكثفة على وحدة المعالجة المركزية:
const { parentPort } = require("worker_threads");
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
السطر الأول يقوم بتحميل وحدة worker_threads
واستخراج فئة parentPort
. توفر الفئة طرق يمكنك استخدامها لإرسال رسائل إلى الخط الرئيسي. بعد ذلك ، لديك مهمة مكثفة للمعالجة المركزية توجد حاليًا في وظيفة calculateCount()
في ملف index.js
. في وقت لاحق في هذه الخطوة ، ستقوم بحذف هذه الوظيفة من index.js
.
بعد ذلك ، أضف الشفرة المميزة أدناه:
const { parentPort } = require("worker_threads");
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
parentPort.postMessage(counter);
هنا تقوم باستدعاء طريقة postMessage()
من فئة parentPort
، والتي ترسل رسالة إلى الخط الرئيسي تحتوي على نتيجة المهمة المكثفة للمعالجة المركزية المخزنة في متغير counter
.
احفظ وأغلق الملف. افتح index.js
في محرر النصوص الخاص بك:
- nano index.js
نظرًا لأن لديك بالفعل المهمة المكثفة للمعالجة المركزية في worker.js
، قم بإزالة الشفرة المميزة من index.js
:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/non-blocking/", (req, res) => {
res.status(200).send("This page is non-blocking");
});
function calculateCount() {
return new Promise((resolve, reject) => {
let counter = 0;
for (let i = 0; i < 20_000_000_000; i++) {
counter++;
}
resolve(counter);
});
}
app.get("/blocking", async (req, res) => {
const counter = await calculateCount();
res.status(200).send(`result is ${counter}`);
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
بعد ذلك ، في رد الاستدعاء app.get("/blocking")
، أضف الشفرة التالية لتهيئة الخط:
const express = require("express");
const { Worker } = require("worker_threads");
...
app.get("/blocking", async (req, res) => {
const worker = new Worker("./worker.js");
worker.on("message", (data) => {
res.status(200).send(`result is ${data}`);
});
worker.on("error", (msg) => {
res.status(404).send(`An error occurred: ${msg}`);
});
});
...
أولاً ، قم بإستيراد وحدة worker_threads
وفك حزمة فئة Worker
. داخل رد الاستدعاء app.get("/blocking")
، أنشئ مثيلًا من Worker
باستخدام الكلمة الرئيسية new
التي تليها استدعاء لـ Worker
مع مسار ملف worker.js
كوسيط له. هذا ينشئ خطًا جديدًا ويبدأ تشغيل الشفرة في ملف worker.js
في الخط على أحد النوى الأخرى.
بعد ذلك، يتم تعليق حدث على الكائن worker
باستخدام الطريقة on("message")
للاستماع لحدث الرسالة. عند استلام الرسالة التي تحتوي على النتيجة من ملف worker.js
، يتم تمريرها كمعلمة إلى استدعاء الطريقة، والتي تقوم بإرجاع استجابة إلى المستخدم تحتوي على نتيجة المهمة المرتبطة بوحدة المعالجة المركزية.
بعد ذلك، يتم تعليق حدث آخر على الكائن worker
باستخدام الطريقة on("error")
للاستماع لحدث الخطأ. إذا حدث خطأ، فإن استدعاء الطريقة يقوم بإرجاع استجابة 404
تحتوي على رسالة الخطأ إلى المستخدم.
الملف الكامل الخاص بك سيبدو الآن على النحو التالي:
const express = require("express");
const { Worker } = require("worker_threads");
const app = express();
const port = process.env.PORT || 3000;
app.get("/non-blocking/", (req, res) => {
res.status(200).send("This page is non-blocking");
});
app.get("/blocking", async (req, res) => {
const worker = new Worker("./worker.js");
worker.on("message", (data) => {
res.status(200).send(`result is ${data}`);
});
worker.on("error", (msg) => {
res.status(404).send(`An error occurred: ${msg}`);
});
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
احفظ وأغلق الملف، ثم قم بتشغيل الخادم:
- node index.js
اذهب إلى علامة التبويب http://localhost:3000/blocking
مرة أخرى في متصفح الويب الخاص بك. قبل أن ينتهي التحميل، قم بتحديث جميع علامات التبويب http://localhost:3000/non-blocking
. يجب أن تلاحظ الآن أنها تحمل فورًا دون الانتظار لانتهاء تحميل المسار /blocking
. هذا يحدث لأن المهمة المرتبطة بوحدة المعالجة المركزية تتم تحميلها على خيط آخر، ويتعامل الخيط الرئيسي مع جميع الطلبات الواردة.
الآن، قم بإيقاف الخادم باستخدام CTRL+C
.
الآن بعدما تعرف كيفية جعل مهمة تستهلك وحدة المعالجة المركزية غير مانعة باستخدام خيط العمل، ستستخدم أربعة خيوط عمل لتحسين أداء المهمة التي تستهلك وحدة المعالجة المركزية.
تحسين مهمة تكثيف وحدة المعالجة المركزية باستخدام أربعة خيوط عمل
في هذا القسم، ستقسم المهمة المكثفة لوحدة المعالجة المركزية بين أربعة خيوط عمل بحيث يمكن لها إكمال المهمة بشكل أسرع وتقليل وقت التحميل لمسار /blocking
.
لكي تتمكن من وضع المزيد من الخيوط العاملة في نفس المهمة، ستحتاج إلى تقسيم المهام. نظرًا لأن المهمة تنطوي على الحلق حوالي 20 مليار مرة، ستقسم 20 مليار على عدد الخيوط التي تريد استخدامها. في هذه الحالة، هو 4
. حساب 20_000_000_000 / 4
سيؤدي إلى 5_000_000_000
. لذلك، ستقوم كل خيط بالحلق من 0
إلى 5_000_000_000
وزيادة counter
بمقدار 1
. عندما ينتهي كل خيط، سيقوم بإرسال رسالة إلى الخيط الرئيسي تحتوي على النتيجة. بمجرد أن يتلقى الخيط الرئيسي رسائل من جميع الأربعة خيوط على حدة، ستقوم بدمج النتائج وإرسال رد للمستخدم.
يمكنك أيضًا استخدام نفس النهج إذا كان لديك مهمة تكرر عبر مصفوفات كبيرة. على سبيل المثال، إذا أردت تغيير حجم 800 صورة في دليل، يمكنك إنشاء مصفوفة تحتوي على جميع مسارات ملفات الصور. بعد ذلك، قم بتقسيم 800
على 4
(عدد الخيوط) ودع كل خيط يعمل في نطاق. سيقوم الخيط الأول بتغيير حجم الصور من فهرس المصفوفة 0
إلى 199
، الخيط الثاني من الفهرس 200
إلى 399
، وهكذا.
أولاً، تحقق من أن لديك أربعة أو أكثر من النوى:
- nproc
Output4
قم بنسخ ملف worker.js
باستخدام أمر cp
:
- cp worker.js four_workers.js
سيتم الاحتفاظ بملفي index.js
و worker.js
الحاليين بحالتهما الحالية حتى تتمكن من تشغيلهما مرة أخرى لمقارنة أدائهما مع التغييرات في هذا القسم لاحقًا.
بعد ذلك، قم بفتح ملف four_workers.js
في محرر النصوص الخاص بك:
- nano four_workers.js
في ملف four_workers.js
الخاص بك، أضف الكود المظلل لاستيراد كائن workerdata
:
const { workerData, parentPort } = require("worker_threads");
let counter = 0;
for (let i = 0; i < 20_000_000_000 / workerData.thread_count; i++) {
counter++;
}
parentPort.postMessage(counter);
أولاً، استخرج كائن workerdata
الذي سيحتوي على البيانات المرسلة من الخط الرئيسي عندما يتم تهيئة الخط (الذي ستقوم به قريبًا في ملف index.js
). يحتوي الكائن على خاصية thread_count
التي تحتوي على عدد الخيوط وهو 4
. في الجزء التالي في حلقة for
، يتم قسم القيمة 20_000_000_000
على 4
، مما يؤدي إلى 5_000_000_000
.
احفظ وأغلق ملفك، ثم قم بنسخ ملف index.js
:
- cp index.js index_four_workers.js
افتح ملف index_four_workers.js
في محرر النصوص الخاص بك:
- nano index_four_workers.js
في ملف index_four_workers.js
الخاص بك، أضف الكود المظلل لإنشاء مثيل الخيط:
...
const app = express();
const port = process.env.PORT || 3000;
const THREAD_COUNT = 4;
...
function createWorker() {
return new Promise(function (resolve, reject) {
const worker = new Worker("./four_workers.js", {
workerData: { thread_count: THREAD_COUNT },
});
});
}
app.get("/blocking", async (req, res) => {
...
})
...
أولاً، قم بتعريف ثابت thread_count
الذي يحتوي على عدد الخيوط التي تريد إنشاءها. في وقت لاحق عندما يكون لديك مزيد من النوى على الخادم الخاص بك، سيكون توسيع القيمة لـ thread_count
يشمل عدد الخيوط التي تريد استخدامها.
الوظيفة createWorker()
تنشئ وتعيد promise. ضمن تابع promise callback، قم بتهيئة خيط جديد عن طريق تمرير فئة Worker
مسار الملف إلى ملف four_workers.js
كالمعامل الأول. ثم قم بتمرير كائن كالمعامل الثاني. بعد ذلك، قم بتعيين الكائن بخاصية workerData
التي تحتوي على كائن آخر كقيمتها. وأخيرا، قم بتعيين الكائن بخاصية thread_count
التي قيمتها عدد الخيوط في الثابت THREAD_COUNT
. الكائن workerData
هو الكائن الذي قمت بالإشارة إليه في ملف workers.js
مسبقًا.
للتأكد من أن الpromise يتم حلها أو يقوم بإلقاء خطأ، قم بإضافة السطور المميزة التالية:
...
function createWorker() {
return new Promise(function (resolve, reject) {
const worker = new Worker("./four_workers.js", {
workerData: { thread_count: THREAD_COUNT },
});
worker.on("message", (data) => {
resolve(data);
});
worker.on("error", (msg) => {
reject(`An error ocurred: ${msg}`);
});
});
}
...
عندما يرسل خيط العمل رسالة إلى الخيط الرئيسي، يتم حل الpromise مع البيانات المُرجعة. ومع ذلك، إذا حدث خطأ، يقوم الpromise بإرجاع رسالة خطأ.
الآن بعد أن قمت بتعريف الوظيفة التي تهيئ خيطًا جديدًا وتقوم بإرجاع البيانات من الخيط، ستقوم باستخدام الوظيفة في app.get("/blocking")
لإنشاء خيوط جديدة.
ولكن أولاً، قم بإزالة الشيفرة المميزة التالية، حيث أنك قد قمت بتعريف هذه الوظيفة بالفعل في الوظيفة createWorker()
:
...
app.get("/blocking", async (req, res) => {
const worker = new Worker("./worker.js");
worker.on("message", (data) => {
res.status(200).send(`result is ${data}`);
});
worker.on("error", (msg) => {
res.status(404).send(`An error ocurred: ${msg}`);
});
});
...
بعد حذف الشيفرة، قم بإضافة الشيفرة التالية لتهيئة أربعة خيوط عمل:
...
app.get("/blocking", async (req, res) => {
const workerPromises = [];
for (let i = 0; i < THREAD_COUNT; i++) {
workerPromises.push(createWorker());
}
});
...
أولاً، قم بإنشاء متغير workerPromises
الذي يحتوي على مصفوفة فارغة. بعد ذلك، قم بتكرار العملية مرات عدد قيمة THREAD_COUNT
، والتي تكون 4
. خلال كل تكرار، قم بالاستدعاء لدالة createWorker()
لإنشاء خيط جديد. ثم، قم بإضافة كائن الوعد الذي تعيده الدالة إلى مصفوفة workerPromises
باستخدام طريقة push
في جافا سكريبت. عندما ينتهي الحلق، ستحتوي مصفوفة workerPromises
على أربعة كائنات وعد تم إعادتها كل منها بواسطة استدعاء دالة createWorker()
أربع مرات.
الآن، قم بإضافة الكود المظلل التالي أدناه لانتظار حل الوعود وإرجاع استجابة للمستخدم:
app.get("/blocking", async (req, res) => {
const workerPromises = [];
for (let i = 0; i < THREAD_COUNT; i++) {
workerPromises.push(createWorker());
}
const thread_results = await Promise.all(workerPromises);
const total =
thread_results[0] +
thread_results[1] +
thread_results[2] +
thread_results[3];
res.status(200).send(`result is ${total}`);
});
نظرًا لأن مصفوفة workerPromises
تحتوي على الوعود التي تم إعادتها بواسطة استدعاء createWorker()
، فأنت تضيف بادئة await
لأسلوب Promise.all()
وتستدعي طريقة all()
مع workerPromises
كوسيط لها. تنتظر طريقة Promise.all()
حل جميع الوعود في المصفوفة. عندما يحدث ذلك، يحتوي متغير thread_results
على القيم التي حلت الوعود. نظرًا لأن الحسابات تم تقسيمها بين أربعة عمال، فأنت تقوم بإضافتها جميعًا من خلال الحصول على كل قيمة من thread_results
باستخدام تركيبة القوس. بمجرد الإضافة، قم بإرجاع القيمة الإجمالية إلى الصفحة.
يجب أن يبدو ملفك الكامل الآن بهذا الشكل:
const express = require("express");
const { Worker } = require("worker_threads");
const app = express();
const port = process.env.PORT || 3000;
const THREAD_COUNT = 4;
app.get("/non-blocking/", (req, res) => {
res.status(200).send("This page is non-blocking");
});
function createWorker() {
return new Promise(function (resolve, reject) {
const worker = new Worker("./four_workers.js", {
workerData: { thread_count: THREAD_COUNT },
});
worker.on("message", (data) => {
resolve(data);
});
worker.on("error", (msg) => {
reject(`An error ocurred: ${msg}`);
});
});
}
app.get("/blocking", async (req, res) => {
const workerPromises = [];
for (let i = 0; i < THREAD_COUNT; i++) {
workerPromises.push(createWorker());
}
const thread_results = await Promise.all(workerPromises);
const total =
thread_results[0] +
thread_results[1] +
thread_results[2] +
thread_results[3];
res.status(200).send(`result is ${total}`);
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
احفظ وأغلق ملفك. قبل تشغيل هذا الملف، قم أولاً بتشغيل index.js
لقياس وقت استجابته.
- node index.js
بعد ذلك، قم بفتح نافذة ترمينال جديدة على جهاز الكمبيوتر المحلي الخاص بك وأدخل الأمر الـ “curl” التالي، الذي يقيس مدة استجابة الطريقة “/blocking”:
- time curl --get http://localhost:3000/blocking
يقيس الأمر “time” مدة تشغيل الأمر “curl”. يقوم الأمر “curl” بإرسال طلب HTTP إلى عنوان URL المعطى وتحديد الخيار “–get” يُوجِّه الأمر “curl” لعمل طلب “GET”.
عند تشغيل الأمر، ستبدو نتيجتك مشابهة لهذه:
Outputreal 0m28.882s
user 0m0.018s
sys 0m0.000s
تُظهَر النتيجة المظللة أنه يستغرق حوالي 28 ثانية للحصول على استجابة، وهذا قد يختلف على جهاز الكمبيوتر الخاص بك.
بعد ذلك، قم بإيقاف الخادم باستخدام “CTRL+C” وقم بتشغيل ملف “index_four_workers.js”:
- node index_four_workers.js
قم بزيارة الطريقة “/blocking” مرة أخرى في النافذة الترمينال الثانية:
- time curl --get http://localhost:3000/blocking
ستظهر لك النتيجة المتسقة مع ما يلي:
Outputreal 0m8.491s
user 0m0.011s
sys 0m0.005s
تُظهر النتيجة أنه يستغرق حوالي 8 ثوانٍ، مما يعني أنك قمت بتقليل وقت التحميل بنسبة تقريبية تبلغ 70%.
لقد نجحت في تحسين المهمة المرتبطة بوحدة المعالجة المركزية باستخدام أربعة خيوط عمل. إذا كان لديك جهاز يحتوي على أكثر من أربعة أنوية، قم بتحديث قيمة “THREAD_COUNT” لتلك العدد وستقلل وقت التحميل بشكل أكبر.
استنتاج
في هذا المقال، قمت ببناء تطبيق Node مع مهمة تقييدية لوحدة المعالجة المركزية تحجب الخيط الرئيسي. ثم حاولت جعل المهمة غير محجوبة باستخدام الوعود، ولكن دون جدوى. بعد ذلك، استخدمت وحدة worker_threads
لتفريغ المهمة المقيدة لوحدة معالجة أخرى لجعلها غير محجوبة. وأخيرًا، استخدمت وحدة worker_threads
لإنشاء أربع خيوط لتسريع المهمة المكثفة لوحدة المعالجة المركزية.
كخطوة مقبلة، انظر إلى توثيق Node.js Worker threads لمعرفة المزيد حول الخيارات. بالإضافة إلى ذلك، يمكنك التحقق من مكتبة piscina
، التي تسمح لك بإنشاء مجموعة عمال لمهامك المكثفة لوحدة المعالجة المركزية. إذا كنت ترغب في مواصلة تعلم Node.js، انظر إلى سلسلة الدروس، كيفية البرمجة في Node.js.
Source:
https://www.digitalocean.com/community/tutorials/how-to-use-multithreading-in-node-js