作为一名同步 PHP 开发者,我对异步 PHP 的心得体会
我使用 Laravel 很多年了。我一直使用同步 PHP。一个请求进来,一个进程运行,然后返回一个响应。我从未觉得需要异步。
后来我读到了关于新的 PHP 8.6 Polling API 的内容。它改变了我对 PHP 如何处理任务的看法。
以下是异步工作原理的详细解析。
阻塞 I/O 的问题
当你调用一个 API 时,你的代码会进入等待状态。
示例:$response = Http::get('https://api.example.com');
如果该 API 需要 300ms,你的 PHP 进程在 300ms 内将无所事事。它处于休眠状态,占用着内存并占据了一个 worker 插槽。如果所有的 worker 都在休眠,你的服务器就会停止接收新请求。
异步解决方案
异步允许你在那 300ms 内做其他工作。与其等待,不如运行其他任务。
但你如何知道数据何时到达呢?这就是内核(kernel)发挥作用的地方。
轮询(Polling)的演进
1. select()
PHP 从 PHP 4 版本起就有了 stream_select()。它会询问内核:“这些套接字(sockets)上有任何就绪的数据吗?”
问题在于重新扫描带来的开销(rescan tax)。如果你有 10,000 个连接,每次都必须将整个列表发送给内核。这既缓慢又会触及限制。
2. epoll / kqueue
这是一种内核特性,而非语言特性。Linux 使用 epoll,macOS 使用 kqueue。
内核不再扫描整个列表,而是维护一个就绪列表(ready-list)。它只会告诉你哪些特定的套接字有数据。这可以扩展到数千个连接而不会产生额外成本。
3. Fibers (PHP 8.1)
Fibers 允许你在调用栈中的任何地方暂停一个函数。
Fiber 不会自动唤醒。它就像一段暂停的 YouTube 视频,必须有人调用 $fiber->resume() 才能让它重新播放。
缺失的一环:PHP 8.6
异步 I/O 需要三个部分: • 暂停:Fibers(现已包含在 PHP 核心中) • 决策:Event Loop(纯 PHP 代码) • 感知:Kernel Polling(缺失的一环)
直到现在,PHP 一直缺乏一种原生的方式,在不使用旧工具或 C 扩展的情况下“感知”哪个套接字已就绪。
PHP 8.6 填补了这一空白。它为核心引入了原生的 Polling API。它会在 Linux 上自动使用 epoll,在 Mac 上使用 kqueue。
宏观视角
异步并非魔法。Event loop 本质上就是一段 PHP 代码,用于决定何时对 Fiber 调用 resume()。
Fibers 提供了暂停的能力。
epoll 提供了感知何时恢复运行的智能。
如果你目前只使用同步 PHP,那么今天你不需要更改你的 Laravel 应用。但理解这一模型会让 ReactPHP 或 Amp 等异步库变得更容易掌握。
去构建,而不仅仅是使用。亲自运行代码,看看它是如何工作的。
