php多进程详解,PHP 多进程系列笔记(一)

本系列文章将向大家讲解 pcntl_*系列函数,从而更深入的理解进程相关知识。

PCNTL在PHP中进程控制支持默认是关闭的。您需要使用 --enable-pcntl 配置选项重新编译PHP的 CGI或CLI版本以打开进程控制支持。

Note: 此扩展在 Windows 平台上不可用。

pcntl_fork

intpcntl_fork(void)

用于创建子进程。成功时,在父进程执行线程内返回产生的子进程的PID,在子进程执行线程内返回0。失败时,在父进程上下文返回-1,不会创建子进程,并且会引发一个PHP错误。

fork.php

$pid=pcntl_fork();

if($pid==-1){

//错误处理:创建子进程失败时返回-1.

die('could not fork');

}elseif($pid){

//父进程会得到子进程号,所以这里是父进程执行的逻辑

$id=getmypid();

echo"Parent process,pid {$id}, child pid {$pid}\n";

}else{

//子进程得到的$pid为0, 所以这里是子进程执行的逻辑

$id=getmypid();

echo"Child process,pid {$id}\n";

sleep(10);

}

命令行运行:

$ php fork.php

Parentprocess,pid98,child pid99

Childprocess,pid99

该例里父进程还没有来得及等子进程运行完毕就自动退出了,子进程由 init进程接管。通过 ps-ef|grep php 看到子进程还在运行:

[root@9355490fe5da/]#ps-ef|grep php

root1051016:46pts/000:00:00php fork.php

root10727016:46pts/100:00:00grep php

子进程成为孤立进程,ppid(父进程id)变成1了。如果在父进程里也加个 sleep(5),你会看到子进程ppid本来是大于1的,后来就变成1了。

注:如果是docker环境,孤立进程的ppid可能是0。

pcntl_wait

pcntl_wait()函数用来让父进程等待子进程退出,默认情况下会阻塞主进程。

阻塞模式

紧接着上面的例子,如果想等子进程运行结束后父进程再退出,该怎么办?那就用到 pcntl_wait了。

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

该函数阻塞当前进程,只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。

我们修改代码:

$pid=pcntl_fork();

if($pid==-1){

exit("fork fail");

}elseif($pid){

$id=getmypid();

echo"Parent process,pid {$id}, child pid {$pid}\n";

pcntl_wait($status);

//pcntl_waitpid($pid, $status);

}else{

$id=getmypid();

echo"Child process,pid {$id}\n";

sleep(10);

}

此时再次运行程序,父进程就会一直等待子进程运行结束然后退出。

pcntl_waitpid()和 pcntl_wait()功能相同。前者第一个参数支持指定pid参数,当指定-1作为 pid的值等同于后者。

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

当已知子进程pid的时候,可以使用 pcntl_waitpid()。

这两个函数返回退出的子进程进程号(>1),发生错误时返回-1,如果提供了 WNOHANG 作为option(wait3可用的系统)并且没有可用子进程时返回0。

返回值为退出的子进程进程号时,想了解如何退出,可以通过 $status状态码反应。

非阻塞模式

pcntl_wait()默认情况下会阻塞主进程,直到子进程执行完毕才继续往下运行。如果设置最后一个参数为常量 WNOHANG,那么就不会阻塞主进程,而是继续执行后续代码, 此时 pcntl_waitpid 就会返回0。

示例:

$pid=pcntl_fork();

if($pid==-1){

exit("fork fail");

}elseif($pid){

$id=getmypid();

echo"Parent process,pid {$id}, child pid {$pid}\n";

while(1){

$res=pcntl_wait($status,WNOHANG);

//$res = pcntl_waitpid($pid, $status, WNOHANG);

if($res==-1||$res>0){

sleep(10);//此处为了方便看效果,实际不需要

break;

}

}

}else{

$id=getmypid();

echo"Child process,pid {$id}\n";

sleep(2);

}

该示例里只有一个子进程,看不出来非阻塞的好处,我们修改一下:

$child_pids=[];

for($i=0;$i<3;$i++){

$pid=pcntl_fork();

if($pid==-1){

exit("fork fail");

}elseif($pid){

$child_pids[]=$pid;

$id=getmypid();

echo time()." Parent process,pid {$id}, child pid {$pid}\n";

}else{

$id=getmypid();

$rand=rand(1,3);

echo time()." Child process,pid {$id},sleep $rand\n";

sleep($rand);//#1 故意设置时间不一样

exit();//#2 子进程需要exit,防止子进程也进入for循环

}

}

while(count($child_pids)){

foreach($child_pidsas$key=>$pid){

// $res = pcntl_wait($status, WNOHANG);

$res=pcntl_waitpid($pid,$status,WNOHANG);//#3

if($res==-1||$res>0){

echo time()." Child process exit,pid {$pid}\n";

unset($child_pids[$key]);

}else{

// echo time()." Wait End,pid {$pid}\n";   //#4

}

}

}

#3处首先先去掉 WNOHANG参数,运行:

$ php fork.1.php

1528637334Parentprocess,pid6600,child pid6601

1528637334Childprocess,pid6601,sleep2

1528637334Parentprocess,pid6600,child pid6602

1528637334Childprocess,pid6602,sleep2

1528637334Parentprocess,pid6600,child pid6603

1528637334Childprocess,pid6603,sleep1

1528637336Childprocessexit,pid6601

1528637336Childprocessexit,pid6602

1528637336Childprocessexit,pid6603

我们看到,6603号进程运行时间最短,但是是最后回收。我们再加上 WNOHANG参数,运行:

$ php fork.1.php

1528637511Parentprocess,pid6695,child pid6696

1528637511Childprocess,pid6696,sleep2

1528637511Parentprocess,pid6695,child pid6697

1528637511Childprocess,pid6697,sleep1

1528637511Parentprocess,pid6695,child pid6698

1528637511Childprocess,pid6698,sleep3

1528637512Childprocessexit,pid6697

1528637513Childprocessexit,pid6696

1528637514Childprocessexit,pid6698

6697进程最先回收!说明确实是异步非阻塞的。感兴趣的朋友还可以开启 #4处代码,未使用 WNOHANG参数的时候,里面的代码是不会运行的。

注意: #2处需要注意子进程需要exit,防止子进程也进入for循环。如果没有 exit(),最终创建的子进程不只3个。

检测status函数

在 pcntl_wait和 pcntl_waitpid两个函数中的 $status中存了子进程的状态信息,这个参数可以用于 pcntl_wifexited、 pcntl_wifstopped、 pcntl_wifsignaled、 pcntl_wexitstatus、 pcntl_wtermsig、 pcntl_wstopsig、 pcntl_waitpid这些函数。

代码片段:

while(1){

$res=pcntl_wait($status);

if($res==-1||$res>0){

if(!pcntl_wifexited($status)){

//进程非正常退出

echo"service exit unusally; pid is $pid\n";

}else{

//获取进程终端的退出状态码;

$code=pcntl_wexitstatus($status);

echo"service exit code: $code;pid is $pid \n";

}

if(pcntl_wifsignaled($status)){

//不是通过接受信号中断

echo"service term not by signal;pid is $pid \n";

}else{

$signal=pcntl_wtermsig($status);

echo"service term by signal $signal;pid is $pid\n";

}

if(pcntl_wifstopped($status)){

echo"service stop not unusally;pid is $pid \n";

}else{

$signal=pcntl_wstopsig($status);

echo"service stop by signal $signal;pid is $pid\n";

}

break;

}

参考

1、php多进程 防止出现僵尸进程https://www.cnblogs.com/jkko123/p/6351615.html?utmsource=itdadao&utmmedium=referral2、PCNTL函数族--PHP多进程编程 (转)https://www.cnblogs.com/zox2011/archive/2013/02/19/2917448.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值