资讯专栏INFORMATION COLUMN

php pcntl 实践填坑

魏明 / 993人阅读

摘要:多进程的主进程创建了子进程,那主进程如何确认子进程的状态呢。假如主进程需要根据子进程的状态做不同的处理呢,这里的状态包括子进程被掉,或变成僵尸进程等。

PHP 可以通过pcntl 扩展实现多进程编程, 而网上关于如何通过pcntl 创建多进程的在这里就不表了, 我主要说说关于pcntl_fork的一个坑和相关的比较生僻的几个函数的使用方式, 这也是通过挖坑和填坑得出的结论。
闲言碎语不要讲, 直接开始

pcntl_fork

在实践中, 我在使用php进行多进程实践的模型大概如下, 期待的是每个子进程都能创建一个与之对应文件, 最后父进程创建一个属于父进程的文件,代码如下(有坑):

$pid_dir = __dir__."/pid_files";
for($i=0; $i<3; $i++){
    $pid = pcntl_fork();
    if($pid == -1){
        var_dump("fork failed");
    }
    if(!$pid){
        //子进程代码
        $pid = posix_getpid();
        $ppid = posix_getppid();
        $r = rand(0,100);  //随机数
        touch("$pid_dir/fork_child_process_{$i}_{$ppid}_{$pid}_{$r}");
    }
} 
$pid = posix_getpid();
$ppid = posix_getppid();
$r = rand(0,100); //随机数
touch("$pid_dir/fork_process_pid_{$ppid}_{$pid}_$r");

上面的代码我通过循环创建3个子进程, 每个进程创建一个文件,完成后到最后, 父进程创建一个属于他自己的文件,所以, 最后应该会创建出4个文件, 但事实并非如此:

fork_child_process_0_62656_62658_39
fork_child_process_1_62656_62659_51
fork_child_process_1_62658_62660_22
fork_child_process_2_62656_62661_91
fork_child_process_2_62658_62662_22
fork_child_process_2_62659_62663_82
fork_child_process_2_62660_62664_59
fork_process_pid_62225_62656_48
fork_process_pid_62656_62658_22
fork_process_pid_62656_62659_82
fork_process_pid_62656_62661_65
fork_process_pid_62658_62660_59
fork_process_pid_62658_62662_59
fork_process_pid_62659_62663_61
fork_process_pid_62660_62664_10

为何会出现上面的结果, 这是因为在fork之后, 原有的进程会分裂为两个进程, 一个主进程, 一个子进程, fork后面所有的代码都是共享的, 虽然通过fork的返回值可以判断是主进程还是子进程来执行相应的子进程或主进程逻辑,但之后子进程自己又走到了for循环的部分, 子进程自己有创建了子进程, 所以上面看到了多个child_process 文件, 至于为什么是7个,
来分析一下。

=====================华丽的分割线=============================

循环变量$i, 当$i为0时, 会产生一个主进程a(不变)和一个子进程aa,这个子进程创建了一个子进程文件,即fork_child_process_0_62656_62658_39, 主进程a继续循环, 即$i=1, 又创建了一个子进程ab, 他创建了fork_child_process_1_62656_62659_51, 主进程a继续循环$i=2, 又创建了一个子进程ac, 他创建了fork_child_process_2_62656_62661_91这里可以看到62656就是主进程a的pid.

至此, 主进程a的循环完毕, 在看看a创建的第一个子进程aa, aa在创建之后, 创建好了上面的子进程文件之后并不会什么也不做, 他也会继续走for的循环, 而且继承了主进程a的循环变量, 也就是$i的值为0,所以aa进程下一次的循环的$i就是1, 然后aa继续创建了子进程aaa,从而创建文件fork_child_process_1_62658_62660_22aa继续,$i=2, 又创建了一个子进程aab, 这个子进程创建了文件fork_child_process_2_62658_62662_22, 这里可以看到aaaaab的ppid就是aa的pid 62658,
同理aaa,aab 也继承了aa的$i值,这时$i的值为1, 当继续循环时, $i 就变成了2, 也就只能循环一次了,相应aaaaab 创建了子进程文件fork_child_process_2_62659_62663_82(aaaa),fork_child_process_2_62660_62664_59(aaba),而他们相应的父进程就是aaa(62659)和aab(62660).

至此, for循环中的多进程逻辑完成了, 也就是为何产生了第一部分的7个文件

=====================华丽的分割线=============================

而至于为何第二部分是8个文件, 各位可以自己思考一下, 注意, 无论主进程还是子进程, 在for循环完毕之后会继续往下走, 知道这一点就好理解了。

在实际的代码中, 我就犯了这种错误。
那如何解决上面的问题呢, 只要在子进程执行的最后exit就好啦,

fork_child_process_0_63219_63221_66
fork_child_process_1_63219_63222_88
fork_child_process_2_63219_63223_22
fork_process_pid_62225_63219_77

继续,那么在网上看到很多多进程编程中使用pcntl_waitpid, 并不了解他是做什么的,且相应的例子很少, 我暂且来说说我的理解

pcntl_waitpid

等待或返回fork的子进程状态。
多进程的主进程创建了子进程,那主进程如何确认子进程的状态呢。 假如主进程需要根据子进程的状态做不同的处理呢, 这里的状态包括子进程被kill掉,或变成僵尸进程等。 pcntl_waitpid就可以获取子进程的状态码, 通过这个状态码, 就可知道子进程处于什么状态
他的用法:

int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )

返回的值可以是-1,0或者 >0的值, 如果是-1, 表示子进程出错, 如果>0表示子进程已经退出且值是退出的子进程pid,至于如何退出, 可以通过$status状态码反应。 那什么时候返回0呢, 只有在option 参数为 WNOHANG且子进程正在运行时0, 也就是说当设置了options=WNOHANG时, 如果子进程还没有退出, 此时pcntl_waitpid就会返回0
另外, 如果不设置这个参数为WNOHANGpcntl_waitpid 就会阻塞运行, 直到子进程退出, 至于option的另外一个值WUNTRACED, 暂未理解, 不表

那么如何根据$status(状态码)判断进程是如何退出呢, 如下(参数都是$status)

pcntl_wifexited

这个函数可以根据$status 判断进程是否正常退出, 何为正常退出, 比如exit

pcntl_wexitstatus

这个函数仅在pcntl_wifexited 返回True(即正常退出)时有效, 且返回子进程退出的返回状态码, 这个返回状态码可以通过exit($s)的参数($s必须为整数时)定义

pcntl_wifsignaled

检查子进程状态码是否代表由于某个信号而中断, 比如是不是我们给他发送了term, int 等信号了

pcntl_wexitstatus

假如是发送信号而导致子进程中断, 那么这个信号是什么信号呢, 这个函数就是获取这个信号的

pcntl_wifstopped

仅当option选项为WUNTRACED时有效, 未理解, 不表

pcntl_wtermsig

同上

综合实例代码:

$res = pcntl_waitpid($pid, $status, WNOHANG);
//FileLog::log("pid is $pid; wait result is $res");
if($res == -1 || $res > 0){
    if(!pcntl_wifexited($status)){
        //进程非正常退出
        FileLog::log("service stop unusally; pid is $pid");
    }else{
        //获取进程终端的退出状态码;
        $code = pcntl_wexitstatus($status);
        FileLog::log("service stop code: $code;pid is $pid ");
    }

    if(pcntl_wifsignaled($status)){
        //不是通过接受信号中断
        FileLog::log("service stop not by signal;pid is $pid ");
    }else{
        $signal = pcntl_wtermsig($status);
        FileLog::log("service stop by signal $signal;pid is $pid");
    }
}

上面的这个代码就通过根据pcntl_waitpid的返回结果和状态码对子进程因为不同原因中断做了不同的处理

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/21087.html

相关文章

  • PHP回顾之多进程编程

    摘要:多进程中与多进程相关的两个重要拓展是和。函数执行期间,主进程除了等待无法处理其他任务,所以一般不认为这是多进程编程。回收子进程有两种方式,一种是主进程调用函数等待子进程结束另外一种是处理信号。 转载请注明文章出处: https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie web响应 session 数据库操作 加解...

    lifesimple 评论0 收藏0
  • PHPpcntl进程控制教程一(pcntl_fork)

    摘要:说明函数创建一个子进程,这个子进程仅进程号和父进程号与其父进程不同。返回值成功时,在父进程执行线程内返回产生的子进程的,在子进程执行线程内返回。失败时,在父进程上下文返回,不会创建子进程,并且会引发一个错误。 pcntl 简介 PHP的进程控制支持实现了Unix方式的进程创建, 程序执行, 信号处理以及进程的中断。 进程控制不能被应用在Web服务器环境,当其被用于Web服务环境时可能会...

    LeexMuller 评论0 收藏0
  • multi-process-pcntl 基于PCNTLPHP并发处理 封装类

    摘要:职场多年下来,技术也算是逐渐地有些积累,但是更重要的是对自身有了更加合理的人生定位。或许,人生的意义,就在于此处的感悟吧。基于的并发处理封装类。对语言底层扩展的的深度解读和生产应用。函数官网手册中对的说明,更细化的需求可以研究深化。 个人声明 作者:于立(wx/yulichenr) 敬告:联系我,请注明来源和来意 本人开发有很多年了,但是很少整理分享,如今趁着清闲就为大家服务了,希...

    awokezhou 评论0 收藏0
  • PHP多进程初探 --- 信号

    摘要:第一次子进程正在休眠中,父进程依旧在循环中。第三次此时父进程已经执行了,将已经退出的子进程回收,释放了等资源。梳理一下流程,子进程向父进程发送信号是对人们来说是透明的,也就是说我们无须关心。 [原文地址:https://blog.ti-node.com/blog...] 上一篇尬聊了通篇的pcntl_wait()和pcntl_waitpid(),就是为了解决僵尸进程的问题,但最后看起来...

    Prasanta 评论0 收藏0

发表评论

0条评论

魏明

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<