将 pcntl 扩展与 PHP-FPM 结合使用是可能的,但不实用
在某些情况下,将 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行字符串,其中包含parent
和child
。看起来是:
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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。