Code前端首页关于Code前端联系我们

将 pcntl 扩展与 PHP-FPM 结合使用是可能的,但不实用

terry 2年前 (2023-09-25) 阅读数 46 #后端开发

在某些情况下,将 pcntl 扩展与 PHP-FPM 结合使用是可能的,但不实用。

pcntl 扩展

pcntl 扩展主要用于进程创建、程序执行、信号处理和 Unix 风格的中断。

手册告知:

不应在 Web 服务器环境中启用 Process Control,如果在 Web 服务器环境中使用 Process Control 功能,可能会出现意外结果。

但是,此扩展非常不可用。在网络服务器的上下文中。是在LNMP环境下使用的吗?让我们来看看。

多进程fork验证

pcntl最常用的功能之一就是fork一个新进程。您可以使用 PHP 函数 pcntl_fork() 来开发多阶段应用程序。

环境

仍然使用Docker来准备环境。使用 OpenResty + PHP 5.6.35。

默认的php:5.6.35-fpm-alpine3.4镜像没有打开pcntl扩展,所以需要更换镜像。 Dockerfile 如下所示:

1
2
3
4
5
6
7
8
FROM php:5.6.35-fpm-alpine3.4
RUN apk add --no-cache --virtual .build-deps g++ make autoconf \
	&& docker-php-source extract \
	&& cd /usr/src/php \
	&& docker-php-ext-configure pcntl \
	&& docker-php-ext-install pcntl \
	&& docker-php-ext-enable pcntl \
	&& apk del --purge .build-deps

构建镜像php:5.6.35-fpm-pcntl-alpine3.4

使用docker-compose配置OpenResty和PHP-FPM,docker-compose.yml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '3'
networks:
    pcntl_test:
services:
    pcntl_test_phpfpm:
        image: php:5.6.35-fpm-pcntl-alpine3.4
        networks:
            - pcntl_test
        volumes:
            - /data/www/htdocs/pcntl_test:/var/www/html
            - /data/www/logs:/var/www/logs
        expose:
          - "9000"

    pcntl_test_openresty:
        image: openresty/openresty:alpine
        networks:
            - pcntl_test
        depends_on:
            - pcntl_test_phpfpm
        links:
            - pcntl_test_phpfpm
        ports:
            - "9527:80"
        volumes:
            - /data/www/etc/pcntl_test/conf/nginx:/etc/nginx/conf.d/:ro
            - /data/www/logs:/usr/local/openresty/nginx/logs
            - /data/www/htdocs/pcntl_test:/var/www/html

测试代码

用于简单脚本编写: 开始测试。

1
curl http://127.0.0.1:9527/index.php

预期的结果是第一个输出为true,并且已经安装了pcntl扩展,然后会出现一个5行字符串,其中包含parentchild 。看起来是:

1
2
3
4
5
6
7
8
9
10
11
true
parent:6 17
parent:6 18
parent:6 19
parent:6 20
parent:6 21
child:21
child:20
child:18
child:19
child:17

现实是:

1
2
3
4
5
6
true
parent:6 17
parent:6 18
parent:6 19
parent:6 20
parent:6 21

猜测与现实之间存在差异。乍一看,fork子进程似乎是不可能的,但是执行过程中到底发生了什么?

echo 被写入文件替换

我们将 echo 替换为写入文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
	$logFn = dirname(__FILE__) . '/log';

	for ($i = 0; $i < 5; ++$i) {
		$pid = pcntl_fork();

		if (0 == $pid) {
			file_put_contents($logFn, "child:" . posix_getpid() . "\n", FILE_APPEND);
			exit();
		} else if ($pid > 0) {
			file_put_contents($logFn, "parent:" . posix_getpid() . " {$pid}\n", FILE_APPEND);
		} else {
			file_put_contents($logFn, posix_getpid() . " error\n", FILE_APPEND);
		}
	}

在响应文件中出现预期结果。

那么可以得出第一个结论:pcntl在某些情况下可以与PHP-FPM一起使用。

为什么子进程回显“失败”

PHP使用IO buffered,有输入输出缓冲区。很可能当WebServer关闭PHP-FPM时,各个进程的集成没有更新。输出缓冲器防止输出被输出。

我们将在原有代码的基础上进行一些修改,添加缓冲区刷新事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
	for ($i = 0; $i < 5; ++$i) {
		$pid = pcntl_fork();

		if (0 == $pid) {
			echo "child:" . posix_getpid() . "\n";
			ob_flush();
			flush();
			exit();
		} else if ($pid > 0) {
			echo "parent:" . posix_getpid() . " {$pid}\n";
			ob_flush();
			flush();
		} else {
			echo posix_getpid() . " error\n";
			ob_flush();
			flush();
		}
	}

这是将子流程文本释放到操作系统的唯一方法。 ?这样的代码。

我们来看看执行前后容器中步数的变化:

1
2
3
4
5
➜ docker top pcntltest_pcntl_test_phpfpm_1 | grep php-fpm | wc -l
3
➜ curl -s http://127.0.0.1:9527/index.php > /dev/null
➜ docker top pcntltest_pcntl_test_phpfpm_1 | grep php-fpm | wc -l
8

可以看到步数增加了5。有人可能会问,他们已经退出()了吗?为什么步数增加了?

PHP 生命周期和 PHP-FPM

PHP 生命周期在 TIPI 中解释得很清楚。可以看到调用exit()后发生了什么:

请求处理之后我们进入最后一步。通常,当脚本执行完毕或者调用exit()或die()函数时,PHP将进入最后阶段。与第一阶段类似,最后阶段也分为两部分。一种是请求完成时关闭模块(RSHUTDOWN,对应RINIT),另一种是SAPI生命周期结束时(Web服务器退出或命令行脚本执行完毕退出)关闭模块。 (MSHUTDOWN,对应于MINIT)。

也就是说,exit()只是到达执行结束,并没有退出进程。 Zend 引擎决定何时退出进程。

PHP-FPM 为了提高处理效率,fork 进程在执行 PHP 代码后不会立即退出。该过程将持续一段指定的时间然后退出。

简而言之,在 PHP-FPM 中使用 pcntl_fork() 期望并行完成任务并不是正确的行为。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门