PHP中异步处理,多线程或多任务实现

在PHP中,有多种方法可以异步处理数据,尽管在每种环境中都无法使用。没有一个真正的解决方案,最适合您的将主要取决于您的特定任务。PHP中的异步处理或多任务, 多线程实现一直都是很多人不理解的地方,都觉得PHP不支持多线程,这是PHP性能低下主要原因。其实错了,PHP是支持多线程的,实现的方式有点不一样。

尽管多线程和多处理都可用于并行处理代码,但首先区分两者可能是有意义的。

为了加快多个任务的执行速度,将工作分成多个线程是有意义的,每个线程执行一个较小的任务。在多核或多个处理器上,这意味着多个处理器可以在一个单一的执行线程中同时完成一部分需要完成的工作,而不是依次完成所有工作。

线程是同一进程的一部分,并且通常将共享相同的内存和文件资源。如果处理不当,可能会导致意外结果,例如比赛条件或死锁。但是在PHP中,情况并非如此:不共享内存,尽管仍然有可能影响另一个线程中的数据。

1. pthreads

PHP文档

PHP中唯一的多线程解决方案是pthreads扩展。以最简单的形式,您将编写如下代码来异步执行工作:

线程的最基本形式通过异步处理获得的结果也可以通过多处理获得。我们在这里所做的只是将工作分成2个线程,最终在完成后将第二个线程的结果处理到原始线程中。如果需要在线程之间传输数据或通过sync(),notify()和wait()使两个线程中的多个步骤的执行保持同步,则与多处理相比,线程确实具有优势。

pthreads是一个PECL扩展,与ZTS(Zend线程安全)PHP 5.3及更高版本兼容。它不是PHP核心的一部分,因此您必须这样pecl install pthreads做。

有关如何使用线程的一些高级示例,请查看GitHub页面

Amp\Thread

Amp \ Thread是pthread及其Amphp异步多任务框架特别有趣的实现。

这个项目的妙处在于,它将复杂的异步工作隐藏在基于Promise的界面后面,

Amp / Thread是专门为CLI应用程序设计的。您需要安装PHP5.5 +和pthreads。

一个进程是1个独立的应用程序运行。虽然一个PHP进程可以生成另一个进程,但是两个进程将完全隔离,并且不会共享任何内存或句柄,这使得实际上很难在它们之间同步数据(尽管,例如,使用外部资源,并非完全不可能)。

2. pcntl_fork


PHP文档

分叉一个进程将导致该请求被克隆到一个精确的副本中,尽管它具有自己的地址空间。直到分叉之前,父进程和子进程(分叉)都将完全相同,例如:到此为止的任何变量在两个进程中都将完全相同。分叉之后,在一个过程中更改变量的值不会影响另一过程。

对于并行处理数据,多处理可能是一个完美有效的解决方案。但是,它不是多线程的一对一替代:完全是一种单独的技术,并且两者都恰好对多任务有用。而且尽管多线程使同步线程或从父代到子代交换数据变得更加容易,但也可以通过外部资源(例如,通过文件,数据库,缓存)手动进行多处理。但是要小心同时请求!

请注意,如果PHP作为Apache模块运行,则pcntl_fork将不起作用,在这种情况下,该功能将不存在!

spatie/async

该库为PHP的PCNTL扩展提供了一个小型且轻松的包装器。它使用易于使用的API允许并行运行不同的进程。

3. popen

PHP文档

虽然我们已经看到了两种将一个请求分成两个不同的执行路径(通过线程或分叉)的策略,但是我们也可以启动一个新请求。在这里,父子进程之间的通信也将更加困难。

child.php

/*
 * This is the child process, it'll be launched
 * from the parent process.
 */

/* Do some expensive work */

parent.php

/*
 * This is the process being called by the user.
 * From here, we'll launch child process child.php
 */

// open child process
$child = popen('php child.php', 'r');

/*
 * Do some expensive work, while already doing other
 * work in the child process.
 */

// get response from child (if any) as soon at it's ready:
$response = stream_get_contents($child);

这是一个非常简单的示例:子进程将在没有任何上下文的情况下启动。但是,您也可以传递一些与子脚本相关的参数。例如,将一个文件名传递给子脚本,这可以为该脚本添加一些有关其应处理的内容的上下文。popen('php child.php -f filename.txt', 'r');

调用后popen,父脚本将继续执行,而无需等待子进程完成。它只会等待子进程直到stream_get_contents被调用。

但是,如果要使stream_get_contents阻止父级的执行,则可以添加stream_set_blocking($ child,0)。在未阻塞的流完成之前获取其stream_get_contents,只会导致部分响应。要读取完整的响应,应将子流上的所有stream_get_contents串联起来,直到feof($ child)返回true。只有这样,父进程才能知道子进程已完成。

请注意,要弹出的命令将取决于您的环境。安装的二进制文件或路径可能有所不同,尤其是在操作系统之间。

fopen/curl/fsockopen

如果不确定环境是否可以运行,则popen可能无法选择运行该软件:所需的命令可能不存在或受限制。与该popen方法类似,Web应用程序可以将单独的子进程触发到运行当前请求的Web服务器。

可以使用多种功能,但有其局限性:
* fopen是最容易实现的功能,但是如果allow_url_fopen设置为false 则将不起作用,
* curl可能不会在所有环境中都安装,
* fsockopen不管,都应始终起作用allow_url_fopen,但是对于孩子的请求和响应,实现起来要困难得多,因为您必须处理原始标头。

这种方法看起来与popen解决方案非常相似。对于fopen,这将是:

child.php

/*
 * This is the child process, it'll be launched
 * from the parent process.
 */

/* Do some expensive work */

parent.php

/*
 * This is the process being called. From here,
 * we'll launch child process child.php
 */

// open child process
$child = fopen('http://'.$_SERVER['HTTP_HOST'].'/child.php', 'r');

/*
 * Do some expensive work, while already doing other
 * work in the child process.
 */

// get response from child (if any) as soon at it's ready:
$response = stream_get_contents($child);
php速查手册
php8 发布时间表