什么是异步编程?
当使用PHP编写的应用程序I/O任务时,程序会在执行某个任务之前,一定要等待之前的任务完成,这时CPU会有很多时间处于空闲状态,这不仅会降低应用程序性能,还会降低硬件利用率。比如,当程序需要从数据库中读取大量的数据时,由于需要等待I/O操作完成,程序的执行速度会非常缓慢。
因此,我们通过事件库,在程序执行的过程中,不需要等待某个任务完成才能执行下一个任务。这种编程模式可以极大地提高程序的效率和响应速度,尤其在处理复杂的I/O操作时表现得更为出色,而这就是异步编程。
Amphp
Amphp/Amp 是一个轻量级、高效的PHP异步库,为开发人员提供了一种处理I/O密集型任务和网络操作的新方式。它基于coroutine(协程)模型,让你能够编写出并发执行的任务,从而最大化服务器资源利用率,提高应用性能。
核心技术
Amp的核心是它的事件循环和coroutine(协程)支持。事件循环监听系统级别的事件,如文件描述符的状态变化或定时器触发,而coroutine则允许代码在不阻塞主线程的情况下进行暂停和恢复。这种设计使得开发者可以以同步代码的风格编写异步程序,降低了异步编程的学习曲线。
此外,Amp还提供了Promise/Try机制,这是一套处理异步操作成功与失败的工具。通过Promise对象,你可以轻松地链式处理异步操作,并优雅地处理错误。
应用场景
- 网络I/O: Amp非常适合处理大量HTTP请求、TCP连接或其他网络通信,如:Websocket。它可以并行处理这些连接,显著提升Web服务的吞吐量。
- 数据库交互: 异步数据库操作可以大大提高数据读取和写入的速度,尤其是在需要处理多个查询时。
- 文件系统操作: 读写大文件或者遍历大量目录时,Amp可以通过异步操作避免阻塞主线程。
- 后台任务: 对于耗时较长的后台任务,如数据处理、爬虫或批量更新,Amp可以实现更快的执行速度。
AMPHP是一个事件驱动的PHP库集合,设计时考虑了纤程和并发性。amphp/amp专门提供了future和cancellation作为异步编程的基本原语。我们现在使用Revolt,而不是使用amphp/amp发布事件循环实现。
PHP大量使用PHP 8.1附带的纤程来编写异步代码,就像同步、阻塞代码一样。与早期版本相比,不需要基于生成器的协程或回调。与线程类似,每个纤程都有自己的调用堆栈,但纤程由事件循环协同调度。使用Amp\async()并发运行。
动机
传统上,PHP遵循顺序执行模型。PHP引擎按顺序一行接一行地执行。然而,程序通常由多个独立的子程序组成,这些子程序可以同时执行。
如果查询数据库,则以阻塞方式发送查询并等待数据库服务器的响应。一旦你有了答案,你就可以开始做下一件事。我们可以发送下一个数据库查询,或者对一个API执行HTTP调用,而不是坐在那里什么也不做。让我们利用我们通常花在等待I/O上的时间!
Revolt允许这样的并发I/O操作。我们通过避免回调来保持低认知负荷。我们的API可以像任何其他库一样使用,除了它们也可以并发工作,因为我们在后台使用了非阻塞I/O。使用Amp\async()并发运行,并在需要时使用Future::await()等。
多年来,在PHP中实现并发的技术有很多,例如PHP 5中的回调和生成器。这些方法都有“你的函数是什么颜色”的问题,我们通过PHP 8.1中的Fibers解决了这个问题。它们允许多个独立调用堆栈的并发性。
纤程由事件循环协同调度,这就是为什么它们也被称为协程。重要的是要理解,在任何给定的时间只有一个协程在运行,所有其他协程在此期间暂停。
你可以将协程比作一台使用单个CPU内核运行多个程序的计算机。每个程序都有一个执行时间段。然而,协程并不是抢占式的。他们没有固定的时间。他们必须主动给予事件循环的控制权。
任何阻塞I/O函数在等待I/O时阻塞整个进程。你会想要避开他们。如果你还没有阅读安装指南,可以看看Hello World示例,它演示了阻塞函数的效果。AMPHP提供的库避免了I/O阻塞。
安装
此包可以作为Composer依赖项安装。
composer require amphp/amp
如果您使用这个库,很可能希望使用Revolt来调度事件,您应该单独要求Revolt,即使它是作为依赖项自动安装的。
composer require revolt/event-loop
这些包为PHP中的异步/并发应用程序提供了基本的构建块。我们提供了很多建立在这些基础上的软件包。
例如以下
- amphp/byte-stream提供流抽象
- amphp/socket为UDP和TCP(包括TLS)提供套
- amphp/parallel提供并行处理以利用多个CPU内核并卸载阻塞操作
- amphp/http-client提供HTTP/1.1和HTTP/2客户端
- amphp/http-server提供HTTP/1.1和HTTP/2应用服务器
- amphp/mysql和amphp/postgres用于非阻塞数据库访问
要求
此软件包需要PHP 8.1或更高版本。无需扩展!仅当应用需要大量并发套接字连接时才需要扩展,通常此限制配置为最多1024个文件描述符。
使用
协程
协同程序是可中断的功能。在PHP中,它们可以使用纤程来实现。
以前版本的JavaScript使用生成器来实现类似的目的,但是纤程可以在调用堆栈中的任何地方中断,这使得以前的样板文件(如Amp\call())变得不必要。
在任何给定的时间,只有一个纤程在运行。当协程挂起时,协程的执行会暂时中断,允许其他任务运行。一旦计时器到期,流操作可能,或任何等待的Future完成,执行将恢复。
协同程序的低级挂起和恢复由Revolt的SuspensionAPI处理。
<?php
require __DIR__ . '/vendor/autoload.php';
use Revolt\EventLoop;
$suspension = EventLoop::getSuspension();
EventLoop::delay(5, function () use ($suspension): void {
print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;
$suspension->resume(null);
});
print '++ Suspending to event loop...' . PHP_EOL;
$suspension->suspend();
print '++ Script end' . PHP_EOL;
在Revolt事件循环上注册的回调会自动作为协程运行,挂起它们是安全的。除了事件循环API,Amp\async()还可以用来启动独立的调用栈。
<?php
use function Amp\delay;
require __DIR__ . '/vendor/autoload.php';
Amp\async(function () {
print '++ Executing callback passed to async()' . PHP_EOL;
delay(3);
print '++ Finished callback passed to async()' . PHP_EOL;
});
print '++ Suspending to event loop...' . PHP_EOL;
delay(5);
print '++ Script end' . PHP_EOL;