ความจริงเกี่ยวกับ Async PHP: Fibers, epoll และ PHP 8.6

ผมทำงานกับ Laravel มาหลายปี ผมใช้ PHP แบบ sync เมื่อมี request เข้ามา process ก็จะทำงาน และส่ง response กลับไป ผมไม่เคยจำเป็นต้องใช้ async code เลย

จนกระทั่งผมได้อ่านเกี่ยวกับ PHP 8.6 Polling API ตัวใหม่ มันเปลี่ยนมุมมองที่ผมมีต่อทุกอย่างไปเลย

และนี่คือสิ่งที่ผมได้เรียนรู้เกี่ยวกับกลไกการทำงานเบื้องหลังของ async

ปัญหาเรื่อง IO

เมื่อคุณเรียกใช้ API, process ของ PHP จะต้องรอ ตัวอย่าง: $response = Http::get('https://api.example.com');

หากการเรียกนั้นใช้เวลา 300ms, CPU ของคุณจะไม่ได้ทำอะไรเลยเป็นเวลา 300ms มันจะอยู่ในสถานะ sleep นี่คือสิ่งที่เรียกว่า blocking I/O

หากคุณมีการเรียก API สามครั้ง:

  • API A: 300ms
  • API B: 400ms
  • API C: 200ms

รวมแบบ Sequential: 900ms. รวมแบบ Async: 400ms (ใช้เวลาเท่ากับตัวที่ช้าที่สุด).

Async ช่วยให้ process ของคุณสามารถไปทำงานอื่นได้ในขณะที่กำลังรอข้อมูล

Select vs. epoll

ในการทำ async คุณจำเป็นต้องรู้ว่า socket ไหนที่มีข้อมูลพร้อมใช้งานแล้ว

  1. select() PHP ใช้ stream_select() มาตั้งแต่เวอร์ชัน 4 โดยมันทำงานด้วยการขอให้ kernel คอยเฝ้าดูรายการของ sockets ปัญหาคือ: ทุกครั้งที่มีข้อมูลมาถึง คุณต้องสแกนรายการทั้งหมดใหม่อีกครั้ง นี่คือ rescan tax (ภาษีการสแกนใหม่) นอกจากนี้ยังมีข้อจำกัดอยู่ที่ประมาณ 1,024 การเชื่อมต่อ

  2. epoll (Linux) / kqueue (macOS) สิ่งเหล่านี้คือฟีเจอร์ของ kernel แทนที่จะสแกนรายการ kernel จะเก็บ ready-list ไว้ และจะบอกคุณเฉพาะ socket ที่พร้อมใช้งานเท่านั้น วิธีนี้สามารถรองรับการเชื่อมต่อได้เป็นพันๆ โดยไม่ต้องทำงานหนักเพิ่มขึ้น

epoll ไม่ใช่ฟีเจอร์ของ PHP แต่มันคือฟีเจอร์ของ Linux ทั้ง Go, Rust และ Node.js ต่างก็ใช้งานมัน

Fibers: ปุ่มกดหยุดชั่วคราว

PHP 8.1 ได้แนะนำ Fibers ขึ้นมา ตอนแรกผมคิดว่า Fibers จะตื่นขึ้นมาเองได้ แต่มันไม่ใช่แบบนั้น

Fiber เปรียบเสมือนวิดีโอที่ถูกกดหยุด (pause) มันจะหยุดอยู่แบบนั้นจนกว่าจะมีใครบางคนเรียก $fiber->resume()

Event Loop ก็เป็นเพียงโค้ด PHP ส่วนหนึ่งที่ทำหน้าที่ตัดสินใจว่าจะเรียก resume() เมื่อไหร่

Async I/O ต้องใช้ 3 ส่วนประกอบ:

  • Pause (หยุด): Fibers (แกนหลักของ PHP 8.1)
  • Decide (ตัดสินใจ): Event Loop (โค้ด PHP ทั่วไป)
  • Know (รู้): Kernel Polling (epoll/kqueue)

ก่อนจะมี PHP 8.6, PHP มีส่วน "Pause" และ "Decide" อยู่แล้ว แต่ส่วน "Know" ยังต้องพึ่งพา select() แบบเก่า หรือไม่ก็ต้องใช้ C extensions ที่ทำงานช้า

PHP 8.6 เข้ามาปิดช่องว่างนี้ โดยการนำ Polling API แบบ native เข้ามาไว้ในแกนหลัก (core) ทำให้ตอนนี้ PHP สามารถใช้งาน epoll หรือ kqueue ได้โดยตรงโดยไม่ต้องใช้ extension เสริม

บทสรุป

หากคุณใช้ Laravel ร่วมกับ PHP-FPM คุณยังไม่จำเป็นต้องเปลี่ยนอะไรในตอนนี้

แต่จงเข้าใจสิ่งนี้: Async ไม่ใช่เวทมนตร์ มันเป็นเพียงวิธีการจัดการเวลาที่ต้องรออย่างชาญฉลาดเท่านั้น

เลิกแค่เสพโค้ดเพียงอย่างเดียว ลองรันสคริปต์ง่ายๆ ลองทำให้มันพัง นั่นแหละคือวิธีที่คุณจะเรียนรู้ได้อย่างแท้จริง

ที่มา: https://dev.to/alamriku/sync-php-developer-hisebe-async-php-bujhte-giye-yaa-shikhlaam-fibers-epoll-aar-php-86-462j