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

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

如果自带的PHP没有安装pcntl扩展,可以下载相同版本的源码,进入ext/pcntl使用phpize编译安装。

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

pcntl_fork

int pcntl_fork ( void )

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

fork.php

<?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
Parent process,pid 98, child pid 99
Child process,pid 99

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

[root@9355490fe5da /]# ps -ef | grep php
root 105 1 0 16:46 pts/0 00:00:00 php fork.php
root 107 27 0 16:46 pts/1 00:00:00 grep php

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

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

pcntl_wait

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

阻塞模式

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

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

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

我们修改代码:

<?php 

$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的值等同于后者。

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

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

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

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

非阻塞模式

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

示例:

<?php 

$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);
}

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

<?php 

$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_pids as $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
1528637334 Parent process,pid 6600, child pid 6601
1528637334 Child process,pid 6601,sleep 2
1528637334 Parent process,pid 6600, child pid 6602
1528637334 Child process,pid 6602,sleep 2
1528637334 Parent process,pid 6600, child pid 6603
1528637334 Child process,pid 6603,sleep 1
1528637336 Child process exit,pid 6601
1528637336 Child process exit,pid 6602
1528637336 Child process exit,pid 6603

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

$ php fork.1.php
1528637511 Parent process,pid 6695, child pid 6696
1528637511 Child process,pid 6696,sleep 2
1528637511 Parent process,pid 6695, child pid 6697
1528637511 Child process,pid 6697,sleep 1
1528637511 Parent process,pid 6695, child pid 6698
1528637511 Child process,pid 6698,sleep 3
1528637512 Child process exit,pid 6697
1528637513 Child process exit,pid 6696
1528637514 Child process exit,pid 6698

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

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

检测status函数

pcntl_waitpcntl_waitpid两个函数中的$status中存了子进程的状态信息,这个参数可以用于 pcntl_wifexitedpcntl_wifstoppedpcntl_wifsignaledpcntl_wexitstatuspcntl_wtermsigpcntl_wstopsigpcntl_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. pcntl_wifexited — 检查状态代码是否代表一个正常的退出。当子进程状态代码代表正常退出时返回 TRUE ,其他情况返回 FALSE 。pcntl_wexitstatus()

    返回一个中断的子进程的返回代码。这个函数仅在函数 pcntl_wifexited() 返回 TRUE 时有效。
  2. pcntl_wifsignaled — 检查子进程状态码是否代表由于某个信号而中断。如果子进程是由于某个未捕获的信号退出的返回 TRUE ,其他情况返回 FALSE 。cntl_wtermsig返回导致子进程中断的信号编号。这个函数仅在 pcntl_wifsignaled() 返回 TRUE 时有效。
  3. pcntl_wifstopped — 检查子进程当前是否已经停止。如果子进程当前是停止的返回 TRUE ,其他情况返回 FALSE 。pcntl_wstopsig ()

    返回导致子进程停止的信号编号。这个函数仅在 pcntl_wifstopped() 返回 TRUE 时有效。

参考

1、php多进程 防止出现僵尸进程

https://www.cnblogs.com/jkko123/p/6351615.html?utm_source=itdadao&utm_medium=referral

2、PCNTL函数族--PHP多进程编程 (转)

https://www.cnblogs.com/zox2011/archive/2013/02/19/2917448.html

PHP多进程系列笔记(一)的更多相关文章

  1. PHP多进程系列笔记(五)

    前面几节都是讲解pcntl扩展实现的多进程程序.本节给大家介绍swoole扩展的swoole_process模块. swoole多进程 swoole_process 是swoole提供的进程管理模块, ...

  2. PHP多进程系列笔记(三)

    本节讲解几个多进程的实例. 多进程实例 Master-Worker结构 下面例子实现了简单的多进程管理: 支持设置最大子进程数 Master-Worker结构:Worker挂掉,Master进程会重新 ...

  3. PHP多进程系列笔记(二)

    上一篇文章讲解了pcntl_fork和pcntl_wait两个函数的使用,本篇继续讲解PHP多进程相关新知识. 僵尸(zombie)进程 这里说下僵尸进程: 僵尸进程是指的父进程已经退出,而该进程de ...

  4. PHP多进程系列笔记(四)

    本节主要讲解Posix常用函数和进程池的概念,也会涉及到守护进程的知识.本节难度较低. Posix常用函数 posix_kill 向指定pid进程发送信号.成功时返回 TRUE , 或者在失败时返回 ...

  5. Java系列笔记(2) - Java RTTI和反射机制

    目录 前言 传统的RTTI 反射 反射的实现方式 反射的性能 反射与设计模式 前言 并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这 ...

  6. Java系列笔记(4) - JVM监控与调优

    目录 参数设置收集器搭配启动内存分配监控工具和方法调优方法调优实例     光说不练假把式,学习Java GC机制的目的是为了实用,也就是为了在JVM出现问题时分析原因并解决之.通过学习,我觉得JVM ...

  7. Java系列笔记(3) - Java 内存区域和GC机制

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  8. 跟着鸟哥学Linux系列笔记3-第11章BASH学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 跟着鸟哥学Linux系列笔记2-第10章VIM学习 认识与学习bash 1. ...

  9. 跟着鸟哥学Linux系列笔记2-第10章VIM学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 常用的文本编辑器:Emacs, pico, nano, joe, vim VI ...

随机推荐

  1. 20155326 2016-2017-2 《Java程序设计》第7周学习总结

    20155326 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 Lambda (1)如果使用JDK8的话,可以使用Lambda特性去除重复的信息. (2)在 ...

  2. bash获得脚本当前路径

    #!/bin/bashCMD_PATH=`dirname $0`echo "current cmd path:$CMD_PATH"cd $CMD_PATHecho $PWDecho ...

  3. Android多线程操作sqlite(Sqlite解决database locked问题)

    参考http://blog.csdn.net/sdsxleon/article/details/18259973  很好 https://github.com/2point0/Android-Data ...

  4. 第32讲:List的基本操作实战与基于模式匹配的List排序算法实现

    今天来学习一下list的基本操作及基于模式匹配的排序操作 让我们从代码出发 val bigData = List("hadoop","spark") val d ...

  5. Maven的插件管理

    <pluginManagement> 这个元素和<dependencyManagement>相类似,它是用来进行插件管理的. 在我们项目开发的过程中,也会频繁的引入插件,所以解 ...

  6. AngularJS 过滤器 Filter

    过滤器实质是数据转换或过滤,把ViewMode中的数据转化成View层用户友好的信息.可以看做一个函数,负责接收输入,转换成输出,每次参数变化时,它就被执行,输出被视图View使用. 一.基本定义及其 ...

  7. CSS 基础 优先级 选择器 继承

    1.样式优先级 (内联样式)Inline style     >    (内部样式)Internal style sheet     >     (外部样式)External style ...

  8. My first paper is now available online

    A two-grid discretization scheme of non-conforming finite elements for transmission eigenvalues

  9. 两台linux之间建立信任关系,实现免密码ssh远程登录或scp数据上传

    两台linux之间建立信任关系,实现免密码远程登录或数据上传 1.执行ssh-keygen命令,生成建立安全信任关系的证书: linux1上:执行命令  ssh-keygen  -t rsa 在程序提 ...

  10. 迁移桌面程序到MS Store(3)——开机自启动

    迁移桌面程序的时候,有可能你会遇到这么个需求——开机自启动.Windows传统桌面程序的传统陋习.不论什么奇葩软件都想要开机自启动,默认就给你打开,一开机哐哐哐什么雷,什么企鹅都蹦出来,也不管你用不用 ...