【问题标题】:Does PHP 7 handle requests asynchronously by default?PHP 7 默认异步处理请求吗?
【发布时间】:2019-02-28 15:35:42
【问题描述】:

在过去的 3.5 年里,我一直在编写 PHP,我喜欢它。当最近的 PHP v/s Nodejs 热议出现时,总是引起关注的一件事是 Nodejs 实现了非阻塞(或异步)I/O默认情况下,PHP 没有。

但是,当我最近尝试在 PHP 中实现一个简单的异步请求路由器(使用 exec() 函数)并尝试将我的代码与标准(假设是同步请求处理程序脚本)进行对比时,我注意到奇怪的东西。

在进一步解释之前,我希望您看一下下面的代码。

sync.php应该暂停所有进一步的请求,直到当前请求完成处理

<?php
    $a = $argv[1];
    echo "starting: ".$a;
    $ar = array();
    $sum = 0;
    $ul = 15000;
    for($i = 1; $i < $ul; $i++){
        $ar[$i] = $i;
    }
    for($i = 1; $i < $ul;  $i++){
        for($j = 1; $j < $ul; $j++){
            $sum += $ar[$j];
        }
    }
    echo "\n\nDone: ".$a."\nResult: ".$sum."\n";
?>

我做了两种测试。

首先,我在我的电脑上运行了四个终端实例并执行:

php sync.php 1  

所有这些同时

接下来,我打开了我的 XAMPP 服务器,通过serveo 暴露了我的本地主机,并同时从三个不同的设备向这个脚本发出了三个请求。

结果出乎意料。

无论我发送多少请求,所有请求(似乎)都会并行执行,并且结果(几乎)同时显示在所有终端窗口/浏览器选项卡上(考虑到 1-2 秒的延迟在每个终端窗口中手动运行命令时发生)。

这不符合 PHP 实现阻塞 I/O 的事实(这就是我的想法)。相反,我期望的是获得

的总爆发时间
n * 4 seconds

其中 n 是处理每个请求所花费的时间(在我的笔记本电脑上大约 8 秒)。

那么,为什么会异步处理请求?是因为 for 循环 是异步执行的函数之一,还是与阻塞 I/O 模型根本没有任何关系??

【问题讨论】:

  • 单个 PHP 进程 阻塞。这就是为什么网络服务器在 FPM 池或一堆 Apache 工作人员中运行多个。您基本上做了同样的事情 - 您在四个单独的终端窗口中运行了四个单独的 PHP 进程。
  • 是的,PHP 中的服务器是多进程的(而 NodeJs 的缺点是它是单进程的,这可能是他们必须实现异步的原因)
  • @ceejayoz 那么三个不同设备通过浏览器发送的请求呢?
  • @progyammer 如果服务器有 3 个可用的 PHP 工作者,所有三个请求将同时执行。

标签: php asynchronous nonblocking


【解决方案1】:

您误解了正在讨论的有关阻塞 I/O 的内容。

每次从命令行运行 PHP 脚本时,它都会在一个完全独立的进程中执行,该进程不知道机器上运行的任何其他 PHP 脚本。您可以多次执行它,原因与您可以同时运行 Web 浏览器和文本编辑器的原因完全相同 - 操作系统正在一个或多个处理器内核上调度进程。

在 Web 服务器示例中,它稍微复杂一些,但原理相同:您向 Web 服务器发出的每个请求都会创建一个新进程,或者在该进程中创建一个新线程,并且 PHP 脚本会在其中运行。

人们讨论的有关阻塞 I/O 的内容完全不同:当您访问 PHP 代码的外部内容时,例如使用 HTTP 请求获取 Web 内容。想象一下这个循环,使用一个虚构的 HTTP 库:

foreach ( $urls as $url ) {
    $results[] =  $httpClient->fetchUrl($url);
}

如果使用内置 PHP 功能编写,这将停止运行并等待远程服务器响应每次循环。因此,对于 10 个请求,每个请求需要 1 秒,完成循环需要 10 秒。

“非阻塞 I/O”是指实现类似 fetchUrl 的函数,以便它们立即返回,即使 HTTP 响应没有返回。这样,您可以一次运行大量 HTTP 请求:在示例循环中,您将在 1 秒后收到所有 10 个响应。然后,您将有一些方法来获得结果,例如“承诺”。

可以在 PHP 中实现这一点,但默认情况下并没有以用户友好的方式执行此操作的本机函数。

更复杂的是,系统可以为 input 实现此功能:每当您在等待外部的东西时,您可以检查是否有新的 Web 请求进入,并开始处理它。这可以有效地模拟多个线程,这就是 NodeJS 能够处理每个请求一个线程的大量流量的方式。

【讨论】:

    【解决方案2】:

    单个 PHP 进程 阻塞。例如,您可以使用built-in development server,它将启动单进程网络服务器。根据那里的说明:

    Web 服务器只运行一个单线程进程,因此如果请求被阻止,PHP 应用程序将停止。

    如果您有成千上万的人同时访问您的网站,这显然是有问题的,因此有一个简单的解决方案 - 多个 PHP 进程。这可以通过多种方式进行设置 - Apache 将 fire up multiple webserver workers,或者您可以运行 PHP-FPM ("FastCGI Process Manager")。

    只要他们是空闲的工作人员,他们就会并行处理传入的请求。

    【讨论】:

    • 所以,基本上,一旦一个请求被处理,请求“路由器”就可以用来处理进一步的请求——这是 Nodejs 的行为,因为 Js 最初是单线程的——对??
    • @progyammer 有一个父进程将请求分发给各个工作人员。基本结果是相同的 - 一次处理多个请求的能力。
    • 您可以使用 ReactPHP (reactphp.org) 或 AMP (amphp.org) 在单线程网络服务器上运行并行 (HTTP) 请求。他们使用类似于 nodeJS/javascript 的 PHP 承诺。至于低级别,您也可以使用套接字 (php.net/manual/en/sockets.examples.php)。
    猜你喜欢
    • 2012-01-15
    • 2012-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-09
    • 2019-06-04
    相关资源
    最近更新 更多