关于 Async PHP 的真相:Fibers、epoll 与 PHP 8.6

我使用 Laravel 很多年了。我一直使用的是同步(sync)PHP。一个请求进来,进程运行,然后返回响应。我从未需要过异步(async)代码。

后来我读到了关于新的 PHP 8.6 Polling API 的文章。它改变了我看待一切的方式。

以下是我学到的关于异步底层工作原理的内容。

IO 问题

当你调用一个 API 时,你的 PHP 进程会进入等待状态。 示例: $response = Http::get('https://api.example.com');

如果该调用耗时 300ms,你的 CPU 在这 300ms 内无所事事。它处于休眠状态。这就是阻塞式 I/O(blocking I/O)。

如果你有三个 API 调用:

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

串行总计:900ms。 异步总计:400ms(最慢那个调用的时间)。

异步允许你的进程在等待数据时执行其他工作。

select vs. epoll

要实现异步,你需要知道哪个 socket 已准备好数据。

  1. select() PHP 自 4.0 版本起就一直在使用 stream_select()。它的工作原理是请求内核去监听一组 socket 列表。 问题在于:每当数据到达时,你都必须重新扫描整个列表。这是一种重新扫描的开销(rescan tax)。它还有一个大约 1024 个连接的限制。

  2. epoll (Linux) / kqueue (macOS) 这些是内核特性。内核不再扫描列表,而是维护一个就绪列表(ready-list)。它只会告诉你哪些特定的 socket 已就绪。这可以扩展到数千个连接,而无需额外的开销。

epoll 不是 PHP 的特性,而是 Linux 的特性。Go、Rust 和 Node.js 都在使用它。

Fibers:暂停键

PHP 8.1 引入了 Fibers。我原以为 Fibers 会自动唤醒,但事实并非如此。

一个 Fiber 就像一段暂停的视频。它会一直处于暂停状态,直到有人调用 $fiber->resume()

Event Loop(事件循环)仅仅是一段决定何时调用 resume() 的 PHP 代码。

异步 I/O 需要三个部分:

  • 暂停 (Pause):Fibers (PHP 8.1 核心)
  • 决策 (Decide):Event Loop (纯 PHP 代码)
  • 感知 (Know):内核轮询 (epoll/kqueue)

在 PHP 8.6 之前,PHP 已经具备了“暂停”和“决策”部分,但“感知”部分则依赖于旧的 select() 或缓慢的 C 扩展。

PHP 8.6 填补了这一空白。它将原生的 Polling API 引入了核心。现在,PHP 可以直接使用 epoll 或 kqueue,而无需额外的扩展。

总结

如果你目前在 PHP-FPM 环境下使用 Laravel,你今天不需要做任何改变。

但请理解这一点:异步并非魔法。它只是一种管理等待时间的聪明方式。

不要只是被动地消费代码。去运行一个简单的脚本,把它搞坏。这才是你真正学习的方式。

来源:https://dev.to/alamriku/sync-php-developer-hisebe-async-php-bujhte-giye-yaa-shikhlaam-fibers-epoll-aar-php-86-462j