وحدة الخيوط العاملة (Worker Threads) في Node.js

من موسوعة حسوب

مؤشر الاستقرار: 1 - تجريبي

توفر وحدة worker طريقة لإنشاء بيئات متعددة تعمل علي خيوط مستقلة، ولإنشاء قنوات رسائل بينها. ويمكن الوصول إليها باستخدام الراية ‎--experimental-worker flag بالإضافة إلى:

const worker = require('worker_threads');

وتفيد الخيوط العاملة (Workers) في أداء عمليات JavaScript كثيفة الاستخدام لوحدة المعالجة المركزية؛ ويجب ألَّا تستخدم في عمليات الإدخال والإخراج I/O، إذ تتعامل آلياتُ Node.js المدمجة لتنفيذ العمليات بشكل غير متزامن معها بشكل أكثر كفاءة من خيوط Worker. علي عكس العمليات التابعة أو عند استخدام وحدة cluster، يمكن أيضًا أن تشارك الخيوطُ العاملةُ الذاكرةَ بكفاءة عن طريق نقل مثيلات ArrayBuffer أو مثيلات sharingSharedArrayBuffer فيما بينهما.

const {
  Worker, isMainThread, parentPort, workerData
} = require('worker_threads');

if (isMainThread) {
  module.exports = async function parseJSAsync(script) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, {
        workerData: script
      });
      worker.on('message', resolve);
      worker.on('error', reject);
      worker.on('exit', (code) => {
        if (code !== 0)
          reject(new Error(`Worker stopped with exit code ${code}`));
      });
    });
  };
} else {
  const { parse } = require('some-js-parsing-library');
  const script = workerData;
  parentPort.postMessage(parse(script));
}

لاحظ أن هذا المثال يولد خيط Worker لكل استدعاء تحليل parse. ومن الناحية العملية، يوصي بشدة باستخدام مجموعة من الخيوط العاملة (Workers) لهذه الأنواع من المهام، لان العبء المُحمَّل لإنشاء Workers قد يتجاوز على الأرجح الفائدة من تسليم العمل إليها.

worker.isMainThread

أضيف مع الإصدار: v10.5.0.

ويكون true إذا لم تكن هذه الشيفرة تعمل داخل خيط Worker.

worker.parentPort

أضيف مع الإصدار: v10.5.0.

  • من النوع ‎<null> | <MessagePort>‎.

إذا نتج هذا الخيط كـ Worker، سيصبح هذا MessagePort يسمح بالاتصال مع الخيط الأصل. وستكون الرسائل المرسلة باستخدام parentPort.postMessage()‎ متوفرة في الخيط الأصل باستخدام worker.on('message')‎، وستكون الرسائل المرسلة من الخيط الأصل باستخدام worker.postMessage()‎ متوفرة في هذا الخيط باستخدام parentPort.on('message')‎.

worker.threadId

أضيف مع الإصدار: v10.5.0.

من النوع <integer>.

عدد صحيح مُعرِّف للخيط الحالي. إذا توفَّر كائن العامل المطابق، فانه يتوفر كـ worker.threadId.

worker.workerData

أضيف مع الإصدار: v10.5.0.

قيمة JavaScript عشوائية تحتوي علي نسخة من البيانات التي مُرِرت إلى مُنشئ خيط Worker هذا.

الصنف MessageChannel

أضيف مع الإصدار: v10.5.0.

تُمثل مثيلات الصنف worker.MessageChannel قناة اتصالات غير متزامنة، ثنائية الاتجاه. ليس لدي MessageChannel توابع خاصة بها. ويُعطي التابعُ MessageChannel()‎ الجديد كائنًا له الخصائص port1 و port2، والتي تشير إلى مثيلات MessagePort.

const { MessageChannel } = require('worker_threads');

const { port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log('received', message));
port2.postMessage({ foo: 'bar' });
// طباعة: received { foo: 'bar' } from the `port1.on('message')` listener

الصنف: MessagePort

أضيف مع الإصدار: v10.5.0.

يمتد: <EventEmitter>.

تمثل مثيلات الصنف worker.MessagePort إحدى نهايتي قناة اتصالات غير متزامنة، ثنائية الاتجاه. ويمكن استخدامها لنقل البيانات المُهيكَلة، ومناطق الذاكرة و MessagePorts أخرى بين مختلف الـ Workers.

مع استثناء MessagePorts كونه EventEmitters بدلًا من EventTargets، ويطابق هذا التطبيق MessagePorts الخاص بالمتصفح.

الحدث 'close'

أضيف مع الإصدار: v10.5.0.

ينطلق الحدث 'close' بمجرد قطع الاتصال بأي من طرفي القناة.

الحدث 'message'

أضيف مع الإصدار: v10.5.0.

  • value من أي نوع من الأنواع <any>. وهو القيمة المرسلة.

ينطلق الحدث 'message' لأي رسالة واردة، تحتوي علي مُدخَل التابع port.postMessage()‎ المُستنسَخ.

سيتلقى المستمعون علي هذا الحدث نسخة من معامل القيمة كما مُرِر إلى التابع postMessage()‎ ولا توجد وسائط إضافية.

port.close()‎

أضيف مع الإصدار: v10.5.0.

يعطل إرسال المزيد من الرسائل علي أيٍ من طرفي الاتصال. يمكن استدعاء هذا التابع بمجرد معرفة انه لن يحدث المزيد من الاتصالات عبر MessagePort هذا.

port.postMessage(value[, transferList])‎

أضيف مع الإصدار: v10.5.0.

  • value من أي نوع من الأنواع <any>.
  • transferList من النوع ‎<Object[]>‎.

إرسال قيمة JavaScript إلى الجانب المتلقي من هذه القناة. ستتنقل القيمة بطريقة متوافقة مع خوارزمية استنساخ HTML المُهيكلة. ولا سيما أنها قد تحتوي على مراجع دورية وكائنات مثل مصفوفات مكتوبة والتي لا تستطيع واجهات تطبيقات JSON تحويلها إلى سلسلة نصية.

transferList قد تكون قائمة من كائنات ArrayBuffer و MessagePort. ولن تكون قابلة للاستخدام علي الجانب المرسل من القناة بعد النقل (حتى لو لم تكن موجودة في value). وخلافًا للعمليات الأبناء، لا يُدعم نقل المعالجات مثل مقابس الشبكة حاليًا.

إذا احتوي value على مثيلات SharedArrayBuffer، ستكون هذه متاحة من أيٍ من الطرفين. ولا يمكن إدراجها في transferList.

قد لا يزال value يحتوي على مثيلات ArrayBuffer غير الموجودة في transferList؛ في هذه الحالة تنسخ الذاكرة الأساسية بدلًا من نقلها.

لان استنساخ الكائن يستخدم خوارزمية الاستنساخ المُهيكَلة، لا يحتفظ بالخصائص غير القابلة للعد، ومُوصِّلات الخاصية (property accessors)، والنماذج الأولية للكائن. علي وجه الخصوص، ستُقرأ كائنات Buffer على أنها Uint8Arrays عادية عند الطرف المُتلقِّي.

سيُستنسخ الكائن message فورًا، ويمكن تعديله بعد النشر دون وجود تأثيرات جانبية.

للحصول علي مزيد من المعلومات حول آليات التسلسل وإلغاء التسلسل من وراء هذه الـ API، راجع واجهات تطبيق التسلسل في الوحدة v8.

port.ref()‎

أضيف مع الإصدار: v10.5.0.

مقابل unref()‎. استدعاء ref()‎ على منفذ سبق إجراء unref عليه لن يسمح للبرنامج بالانتهاء إذا كان هو المعالج الوحيد المتبقي (السلوك الافتراضي). استدعاء ref()‎ مرة أخرى على منفذ سبق إجراء ref عليه لن يكون له أي تأثير.

إذا كان المستمعون متصلين أو أزيلوا باستخدام ‎.on('message')‎، سيُطبق على المنفذ التوابع ref()‎ و unref()‎‎ تلقائيًا اعتمادًا علي وجود مستمعين للحدث.

port.start()‎

أضيف مع الإصدار: v10.5.0.

بدء تلقي الرسائل علي MessagePort هذا. عند استخدام هذا المنفذ كمُطلِق للحدث، سيُستدعى هذا التابع تلقائيًا بمجرد إرفاق مستمعي الحدث 'message'.

port.unref()‎

أضيف مع الإصدار: v10.5.0.

يسمح استدعاء unref()‎ على منفذ للخيط بالإنهاء إذا كان هو المعالج النشط الوحيد في نظام الأحداث. استدعاء unref()‎ مرة أخرى على منفذ سبق إجراء unref عليه لن يكون له أي تأثير.

إذا كان المستمعون متصلين أو أُزيلوا باستخدام ‎.on('message')‎، سيُطبق على المنفذ التوابع ref()‎ و unref()‎‎ تلقائيًا اعتمادًا علي وجود مستمعين للحدث.

الصنف Worker

أضيف مع الإصدار: v10.5.0.

يمثل الصنف Worker خيط تنفيذ JavaScript مستقل. تتوفر معظم واجهات تطبيقات Node.js داخله.

ومن أبرز هذه الاختلافات داخل بيئة Worker:

  • قد يُعاد توجيه process.stdin و process.stdout و process.stderr بواسطة الخيط الأصل.
  • تُضبط الخاصية require('worker_threads').isMainThread بالقيمة false.
  • يصبح منفذ الرسالة require('worker_threads').parentPort متاحًا،
  • ولا يوقف التابعُ process.exit()‎ البرنامجَ بأكمله، فقط الخيطَ منفردًا، ولا يُتاح process.abort()‎.
  • ولا يُتاح process.chdir()‎ ولا توابع العمليات التي تضبط مُعرِّفات المجموعة أو المستخدم.
  • process.env هو مرجع للقراءة فقط إلى متغيرات البيئة.
  • لا يمكن تعديل process.title.
  • لن تُسلَّم الإشارات من خلال process.on('...')‎‎.
  • قد يتوقف التنفيذ عند أي نقطة نتيجة لاستدعاء التابع worker.terminate()‎.
  • لا يمكن الوصول إلى قنوات IPC من العمليات الأصلية.

وتوجد حاليًا الاختلافات التالية أيضا، إلى أن يتم معالجتها:

  • وحده inspector غير مُتاحة حتى الآن.
  • الإضافات الأصلية غير مدعومة حتى الآن.

يمكن إنشاء مثيلات Worker داخل Worker آخرين.

مثلما هو الحال مع Web Workers ووحدة cluster، يمكن تحقيق الاتصالات في اتجاهين من خلال تمرير الرسائل بين الخيوط. يحتوي Worker داخليَا على زوج مُضمَّن من MessagePorts المقترن بالفعل مع بعضه البعض عند إنشاء الكائن Worker. في حين لا يكون الكائن MessagePort علي جانب الأصل مُستهدَفًا بشكل مباشر، تكون وظائفه مُستهدَفة من خلال التابع worker.postMessage()‎ والحدث worker.on('message')‎ علي الكائن Worker لخيط الأصل.

لإنشاء قنوات المراسلة المخصصة (والتي يُفضَّل استخدامها عن استخدام القناة العامة الافتراضية لأنها تُسهِّل فصل الاهتمامات)، يمكن للمستخدمين إنشاء كائن MessageChannel علي أي خيط وتمرير أحد MessagePorts علي هذه MessageChannel إلى الخيط الآخر من خلال قناة موجودة من قبل، مثل القناة العامة.

راجع port.postMessage()‎ للحصول علي مزيد من المعلومات حول كيفية تمرير الرسائل، وماهية نوع قيم JavaScript التي يمكن نقلها بنجاح عبر حاجز الخيط.

const assert = require('assert');
const {
  Worker, MessageChannel, MessagePort, isMainThread, parentPort
} = require('worker_threads');
if (isMainThread) {
  const worker = new Worker(__filename);
  const subChannel = new MessageChannel();
  worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
  subChannel.port2.on('message', (value) => {
    console.log('received:', value);
  });
} else {
  parentPort.once('message', (value) => {
    assert(value.hereIsYourPort instanceof MessagePort);
    value.hereIsYourPort.postMessage('يرسل العامل هذا');
    value.hereIsYourPort.close();
  });
}
new Worker(filename[, options])‎

filename من النوع <string>، وهو المسار إلى سكريبت Worker الرئيسي. يجب أن يكون إمَّا مسارًا مطلقًا أو مسارًا نسبيًا (أي بالنسبة إلى دليل العمل الحالي) بدءًا من ‎./‎ أو ‎../‎. إذا كانت options.eval قيمتها true، تكون هذه سلسلة نصية تحتوي علي شيفرة JavaScript بدلًا من المسار.

options من النوع <Object>.

  • eval من النوع <boolean>، إذا كان true، تفسر الوسيط الأول للمُنشئ كسكريبت يُنفَّذ فور اتصال العامل.
  • workerdata من أي نوع من الأنواع <any>، أي قيمة JavaScript ستُستنسَخ وتصبح متاحة في الصورة require('worker_threads').workerData. سيحدث الاستنساخ كما هو موضح في خوارزمية استنساخ HTML المُهيكَلة، وسينطلق خطأ إذا كان لا يمكن استنساخ الكائن (علي سبيل المثال لأنه يحتوي علي دوال).
  • stdin من النوع <boolean>، إذا ضُبط بالقيمة true، سيوفر worker.stdin دفق قابل للكتابة والذي تظهر محتوياته كـ process.stdin داخل Worker. بشكل افتراضي، لا تتوفر أي بيانات.
  • stdout من النوع <boolean>، إذا ضُبط بالقيمة true، لن يُوجَّه worker.stdout تلقائيًا من خلاله إلى process.stdout في الأصل.
  • stderr من النوع <boolean>، إذا ضُبط بالقيمة true، لن يُوجَّه worker.stderr تلقائيًا من خلاله إلى process.stderr في الأصل.

الحدث 'error'

أضيف مع الإصدار: v10.5.0.

سينطلق الحدث 'error' إذا أجرى الخيط العامل استثناءً غير مُلتقَط. في هذه الحالة، سيتم إنهاء العامل.

الحدث 'exit'

أضيف مع الإصدار: v10.5.0.

ينطلق الحدث 'exit' بمجرد توقف العامل. إذا أٌُنهي العامل باستدعاء process.exit()‎، سيكون المعامل exitCode هو رمز الإنهاء الذي مُرِر. إذا أُنهي العامل، سيكون المعامل exitCode قيمته 1.

الحدث 'message'

أضيف مع الإصدار: v10.5.0.

  • value من أي نوع من الأنواع <any>. وهو القيمة المُرسَلة.

ينطلق الحدث 'message' عند استدعاء الخيط العامل للتابع require('worker_threads').postMessage()‎. راجع الحدث port.on('message')‎ للحصول علي مزيد من التفاصيل.

الحدث 'online'

أضيف مع الإصدار: v10.5.0.

ينطلق الحدث 'online' عند بدء الخيط العامل في تنفيذ شيفرة JavaScript البرمجية.

worker.postMessage(value[, transferList])‎

أضيف مع الإصدار: v10.5.0.

  • value من أي نوع من الأنواع <any>.
  • transferList من النوع ‎<Object[]>‎.

إرسال رسالة للعامل يستلمها من خلال require('worker_threads').parentPort.on('message')‎. راجع port.postMessage()‎ للحصول علي مزيد من التفاصيل.

worker.ref()‎

أضيف مع الإصدار: v10.5.0.

على عكس unref()‎، استدعاء ref()‎ على عامل سبق إجراء unref عليه لن يسمح للبرنامج بالانتهاء إذا كان هو المُعالج الوحيد المتبقي (السلوك الافتراضي). استدعاء ref()‎ مرة أخرى على عامل سبق إجراء ref عليه لن يكون له أي تأثير.

worker.stderr

أضيف مع الإصدار: v10.5.0.

وهو دفق قابل للقراءة يحتوي علي البيانات المكتوبة على process.stderr داخل خيط العامل. إذا لم تُمرر stderr: true لمُنشِئ Worker، ستُوجَّه البيانات إلى دفق process.stderr لخيط الأصل.

worker.stdin

أضيف مع الإصدار: v10.5.0.

إذا مُررت stdin: true لمُنشِئ Worker، يكون ذلك دفق قابل للكتابة. ستُتاح البيانات المكتوبة إلى دفق process.stdin في الخيط العامل.

worker.stdout

أضيف مع الإصدار: v10.5.0.

وهو دفق قابل للقراءة يحتوي علي البيانات المكتوبة على process.stdout داخل خيط العامل. إذا مُررت stdout: true لمُنشِئ Worker، ستوجه البيانات إلى دفق process.stdout لخيط الأصل.

worker.terminate([callback])‎

أضيف مع الإصدار: v10.5.0.

إيقاف تنفيذ كافة JavaScript في الخيط العامل في أقرب وقت ممكن. callback هو دالة اختيارية تُستدعى بمجرد معرفة اكتمال هذه العملية.

تحذير: حاليًا، ليست جميع الشيفرات البرمجية الداخلية في Node.js مستعدة لتوقع الإنهاء في نقاط عشوائية في الوقت المناسب وقد يتلف إذا واجه هذا الظرف. وبناءً على لذلك، يجب حاليًا فقط استدعاء ‎.terminate()‎ إذا كان من المعروف أن الخيط العامل لا يمكنه الوصول إلى الوحدات الأساسية من Node.js غير المعروض في وحدة العامل.

worker.threadId

أضيف مع الإصدار: v10.5.0.

عدد صحيح مُعرِّف للخيط المشار إليه. داخل الخيط العامل، ويكون متوفرًا في الصورة require('worker_threads').threadId.

worker.unref()‎

أضيف مع الإصدار: v10.5.0.

يسمح استدعاء unref()‎ على عامل للخيط بالإنهاء إذا كان هو المعالج النشط الوحيد في نظام الأحداث. استدعاء unref()‎ مرة أخرى على عامل سبق إجراء unref عليه لن يكون له أي تأثير.

مصادر