المعالجة المتعددة باستخدام دوال العمليات الفرعية المضمنة
يمكنك استخدام الدوال المضمنة لتنفيذ عمليات PHP مثل عمليات فرعية forks، هذه أبسط طريقة لتحقيق عمل متوازٍ إذا كنت لا تحتاج أن تحدث خيوطك threads مع بعضها، يتيح لك هذا وضع المهام التي تستهلك الكثير من الوقت (مثل تحميل ملف إلى خادم آخر أو إرسال بريد إلكتروني) على عاتق خيط آخر لذا تزداد سرعة تحميل السكربت ويمكن استخدام نوى متعددة ولكن انتبه أنّ هذا ليس تعدد خيوط حقيقي وأنّ خيطك الأساسي لن يعرف ما يفعله الخيوط الأبناء.
لاحظ أنّ هذا في نظام التشغيل ويندوز سيُظهر موجه أوامر لكل عملية فرعية تبدأها.
ملف master.php
:
$cmd = "php worker.php 10"; // نستخدم في نظام التشغيل ويندوز popen وpclose if(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { pclose(popen($cmd,"r")); } // نستخدم في أنظمة يونكس shell exec مع علامة "&" في النهاية { exec('bash -c "exec nohup setsid '.$cmd.' > /dev/null 2>&1 &"'); }
ملف worker.php
:
// إرسال رسائل بريد إلكتروني، تحميل ملفات، تحليل سجلات وغير ذلك $sleeptime = $argv[1]; sleep($sleeptime);
إنشاء عملية ابن باستخدام fork
يوجد في PHP الدالة المضمنة pcntl_fork
لإنشاء عملية ابن، وهي مشابهة للدالة fork
في يونكس، لا تأخذ معاملات وترجع عددًا صحيحًا يمكن استخدامه للتمييز بين العملية الأب والعملية الابن، بفرض لدينا الشيفرة التالية:
<?php // $pid معرّف العملية الابن $pid = pcntl_fork(); if ($pid == -1) { die('Error while creating child process'); } else if ($pid) { // العملية الأب } else { // العملية الابن } ?>
كما تلاحظ يدل -1 على حدوث خطأ وعدم إنشاء العملية الابن، عند إنشاء العملية الابن يكون لدينا عمليتان تنفذان ولكل منهما معرِّف عملية PID مختلف.
يجب أن ننتبه هنا أيضًا إلى العملية الميتة zombie process أو العملية المنقطعة defunct process عندما تنتهي العملية الأب قبل العملية الابن. لمنع العملية الابن الميتة نضيفpcntl_wait($status)
في نهاية العملية الأب، إذ توقف هذه الدالة تنفيذ العملية الأب حتى تنتهي العملية الابن. ويجب الانتباه أيضًا إلى أنه لا يمكن قتل العملية الميتة باستخدام العلامة SIGKILL
.
التواصل بين العمليات
يسمح التواصل بين العمليات للمبرمجين بالتواصل بين العمليات المختلفة، فمثلًا إذا احتجنا لكتابة تطبيق PHP يمكنه تنفيذ أوامر bash وطباعة الخرج، سنستخدم الدالة proc_open
التي ستنفذ الأمر وتعيد موردًا يمكننا التواصل معه، تُظهر الشيفرة التالية تنفيذًا بسيطًا ينفذ الأمر pwd
في bash من PHP:
<?php $descriptor = array( // أنبوب من أجل مجرى الدخل القياسي للابن 0 => array("pipe", "r"), // أنبوب من أجل مجرى الخرج القياسي للابن 1 => array("pipe", "w"), ); $process = proc_open("bash", $descriptor, $pipes); if (is_resource($process)) { fwrite($pipes[0], "pwd" . "\n"); fclose($pipes[0]); echo stream_get_contents($pipes[1]); fclose($pipes[1]); $return_value = proc_close($process); } ?>
تنفذ الدالة proc_open
أوامر bash مع $descriptor
كمواصفات للواصف، ونستخدم بعدها is_resource
للتحقق من صحة العملية، وعند الانتهاء يمكننا البدء بالتفاعل مع العملية الابن باستخدام $pipes
الذي يتولد تبعًا لمواصفات الواصف.
بعد ذلك يمكننا استخدام fwrite
للكتابة في مجرى الدخل القياسي للعملية الابن، وفي هذه الحالة يُتبع pwd
بسطر إرجاع.، وأخيرًا نستخدم stream_get_contents
للقراءة من مجرى الخرج القياسي للعملية الابن.
تذكر دومًا إغلاق العملية الابن باستخدام الدالة proc_close()
التي ستنهي العملية الابن وتعيد شيفرة حالة الخروج.
الإضافة متعددة الخيوط (Multi Threading)
للبدء مع تعدد الخيوط تحتاج لتثبيت الإضافة pthreads
ويمكنك القيام بذلك بكتابة:
$ pecl install pthreads
وإضافة المدخل إلى ملف php.ini
.
مثال بسيط باستخدام دلالات PHP7:
<?php class MyThread extends Thread { /** * @var string * متغير يتضمن الرسالة التي ستُعرض */ private $message; public function __construct(string $message) { // ضبط قيمة الرسالة لهذا الكائن المحدد $this->message = $message; } // العمليات التي تؤدى في هذه الدالة تُنفَّذ في الخيط الآخر public function run() { echo $this->message; } } // تهيئة الكائن MyThread $myThread = new MyThread("Hello from an another thread!"); // ابدأ الخيط، ومن الجيد أيضًا الانضمام إلى الخيط بشكلٍ صريح // نستخدم Thread::start() لتهيئة الخيط $myThread->start(); // تسبب التعليمة Thread::join() أن ينتظر السياق الخيط حتى انتهاء التنفيذ $myThread->join();
استخدام المجمعات (pools) والتوابع (workers)
توفر التجميعات مستوى تجريدي أعلى لوظيفة التابع، بما في ذلك إدارة المراجع بالطريقة التي تطلبها pthreads (انظر هذه الصفحة).
توفر المجمعات والتوابع مستوى تحكم أعلى وسهولة في إنشاء تعددية الخيوط.
<?php // (1) class AwesomeWork extends Thread { private $workName; /** * @param string $workName * اسم العمل الذي سيُعطى لكل عمل */ public function __construct(string $workName) { // (2) $this->workName = $workName; printf("A new work was submitted with the name: %s\n", $workName); } public function run() { // (3) $workName = $this->workName; printf("Work named %s starting...\n", $workName); printf("New random number: %d\n", mt_rand()); } } // إنشاء عامل فارغ class AwesomeWorker extends Worker { public function run() { // (4) } } // (5) $pool = new \Pool(1, \AwesomeWorker::class); // (6) $pool->submit(new \AwesomeWork("DeadlyWork")); $pool->submit(new \AwesomeWork("FatalWork")); // (7) $pool->shutdown();
-
في الموضع (1) يوجد العمل الذي سيُنفَّذ من قِبل العامل، يحتاج هذا الصنف أن يرث الصنف
\Threaded
أو\Collectable
أو\Thread
. - في الموضع (2) كتلة الشيفرة في باني عملك والتي ستُنفَّذ عندما يُرسل عمل إلى مجمعك.
- في الموضع (3) ستُستدعى هذه الكتلة من الشيفرة من قِبل التابع، وستُنفَّذ كل شيفرة التابع في خيط آخر.
- في الموضع (4) يمكنك وضع بعض الشيفرة هنا والتي سينفذها العامل قبل بدء العمل.
-
في الموضع (5) إنشاء كائن
Pool
جديد يقبل معاملين، الأول هو عدد العمال الأعظمي الذي يمكن إنشاؤه في المجمع، والثاني هو اسم صنف العامل. -
في الموضع (6) تحتاج إلى إرسال أعمالك بدلًا من نسخة الكائنات التي ترث الصنف
\Threaded
. - في الموضع (7) نحتاج إلى إيقاف المجمع بشكلٍ صريح وإلا قد يحصل شيء ما غير متوقع.
ترجمة -وبتصرف- للفصول [Multiprocessing - Multi Threading Extension] من كتابPHP Notes for Professionals book
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.