تمت الموافقة على مقترح PHP RFC الذي يحدد Polling API أصلي ودمجه في الفرع الرئيسي (master branch) للغة، مما يمنح المطورين وسيلة سريعة على مستوى نظام التشغيل لاستطلاع واصفات الملفات (file descriptors). ومع إضافة Fibers مؤخرًا، أصبح لدى PHP الآن أساس داخلي وفعال للأكواد غير المتزامنة (asynchronous code)—وهي فجوة أجبرت المكتبات لفترة طويلة على ابتكار حلول بديلة خاصة بها.
Fibers وحلقة الأحداث (event loop)، في مسارين منفصلين
تُعد Fibers عنصرًا أساسيًا للتحكم في تدفق العمليات (control-flow primitive) على مستوى منخفض. فهي تسمح للدالة بتعليق تنفيذها عند نقطة مختارة ثم استئنافها لاحقًا من حيث توقفت تمامًا. والأهم من ذلك، أن الـ fiber لا يعرف شيئًا عن المقابس (sockets) أو المؤقتات (timers) أو أي مصدر إدخال/إخراج (I/O) آخر؛ بل يكتفي بالتوقف وإعادة التشغيل عند الطلب.
حلقة الأحداث (event loop) هي المجدول (scheduler) الذي يقرر متى يجب استئناف الـ fiber المتوقف. في بنية البرمجة غير المتزامنة (async stack) النموذجية، تراقب الحلقة مجموعة من واصفات الملفات (file descriptors)، وتنتظر حتى تصبح قابلة للقراءة أو الكتابة، ثم توقظ الـ fiber المناسب.
كان لدى PHP سابقًا Fibers ولكنها كانت تفتقر إلى آلية أصلية لسؤال نظام التشغيل عن الواصفات الجاهزة. وكانت النتيجة الاعتماد على stream_select() أو إضافات خارجية، وانتهى الأمر بكل مكتبة async بكتابة واجهاتها الخلفية (back-ends) الخاصة لـ Linux’s epoll و BSD’s kqueue و Windows’ IOCP وغيرها.
يسد Polling API الجديد تلك الفجوة. فهو يوفر غلافًا بسيطًا (thin wrapper) حول مرافق الاستطلاع (polling facilities) الخاصة بنظام التشغيل (مثل epoll في Linux و kqueue في BSD/macOS، إلخ). لا يحل الـ API محل Fibers؛ بل يمنح حلقة الأحداث السرعة والقابلية للتوسع (scalability) التي تحتاجها فحسب.
لماذا يهم هذا التغيير مكتبات ReactPHP و Amp وأمثالها
قامت ReactPHP و Amp v3 ببناء طبقات تجريد (abstraction layers) خاصة بها فوق آليات الاستطلاع الخاصة بنظام التشغيل. تحتوي هذه الطبقات على مسارات برمجية متعددة، كل منها مضبوط لمنصة معينة، ويجب الحفاظ على مزامنتها مع تغييرات النواة (kernel). ومع وجود Polling API أصلي، يمكن للمكتبات التخلص من معظم تلك التعقيدات البرمجية (plumbing) والاعتماد على استدعاء واحد مقدم من النواة الأساسية.
- الصيانة – وجود فروع أقل خاصة بالمنصات يعني أخطاءً أقل ومساحة أصغر للمراجعات الأمنية.
- الأداء – الاستدعاء الأصلي يتواصل مباشرة مع epoll/kqueue.
- سهولة النقل (Portability) – الكود الذي يعمل على PHP "الخام" (vanilla) يحصل الآن على نفس الأداء الأساسي على جميع أنظمة التشغيل المدعومة، دون الحاجة إلى إضافات اختيارية.
في المقابل، تظل Swoole بديلًا كاملاً لبيئة التشغيل (full-runtime replacement) حيث تأتي مع حلقة الأحداث ونظام الـ coroutine الخاص بها. لا يؤثر Polling API على المقايضات (trade-offs) الخاصة بـ Swoole؛ فالمطورون الذين يحتاجون إلى زمن انتقال منخفض للغاية (ultra-low latency) أو إدارة مخصصة للذاكرة سيظلون يعتبرونها خيارًا منفصلًا.
اختبار أداء سريع يوضح الصورة
قام مجدول بسيط بجلب عدة روابط URL عبر مقابس خام (raw sockets)، باستخدام Io\Poll\Context واحد لإدارة عدة fibers. وقد ظهرت ملاحظتان:
- السرعة – إضافة المزيد من الطلبات المتزامنة لا تزيد من إجمالي الوقت المنقضي؛ حيث تنتهي دفعة الطلبات بنفس سرعة أبطأ طلب فردي. بعبارة أخرى، فإن العبء الإضافي للتزامن (concurrency overhead) يكاد يكون معدومًا.
- تكلفة المعالج (CPU cost) – مع استخدام
stream_select()، يرتفع استهلاك المعالج بشكل ملحوظ مع زيادة عدد التدفقات (streams)، لأن الدالة يجب أن تمر على كل واصف في كل استدعاء. أما تكلفة Polling API فتظل ثابتة من ثلاثة تدفقات إلى ثلاثين، بفضل قدرة النواة على مراقبة العديد من الواصفات في استدعاء نظام واحد.
ما الذي يجب على المطورين فعله الآن
- اعتمد Amp v3 إذا كنت تفضل أسلوبًا أصليًا يعتمد على الـ fiber ويتوافق مباشرة مع الـ API الجديد. فواجهته العامة ترتبط بالفعل بالمستطلع (poller) الأساسي، لذا ستحصل على الفائدة دون الحاجة لإعادة كتابة الكود الخاص بك.
- استمر مع ReactPHP إذا كنت تفضل التحكم الصريح في دورة حياة الحلقة (loop's lifecycle).
- تجنب المجدولات المخصصة لأحمال العمل في بيئة الإنتاج. لا تكتب المجدل الخاص بك لبيئة الإنتاج.
