اذهب إلى المحتوى

المعالجة المتعددة في PHP


سارة محمد2

المعالجة المتعددة باستخدام دوال العمليات الفرعية المضمنة

يمكنك استخدام الدوال المضمنة لتنفيذ عمليات 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


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...