PHP多进程的实际处理
多进程应用大批量的数据是非常舒服的一件事情。
处理之前理解两个概念:孤儿进程和僵尸进程
孤儿进程:
是指父进程在fork出子进程后,自己先完了。这个问题很尴尬,因为子进程从此变得无依无靠、无家可归,变成了孤儿。用术语来表达就是,父进程在子进程结束之前提前退出,这些子进程将由init(进程ID为1)进程收养并完成对其各种数据状态的收集。init进程是Linux系统下的奇怪进程,这个进程是以普通用户权限运行但却具备超级权限的进程,简单地说,这个进程在Linux系统启动的时候做初始化工作,比如运行getty、比如会根据/etc/inittab中设置的运行等级初始化系统等等,当然了,还有一个作用就是如上所说的:收养孤儿进程。
僵尸进程:
是指父进程在fork出子进程,而后子进程在结束后,父进程并没有调用wait或者waitpid等完成对其清理善后工作,导致改子进程进程ID、文件描述符等依然保留在系统中,极大浪费了系统资源。所以,僵尸进程是对系统有危害的,而孤儿进程则相对来说没那么严重。在Linux系统中,我们可以通过ps -aux来查看进程,如果有[Z+]标记就是僵尸进程。
在PHP中:
1、pcntl_fork(),只管fork生产,不管产后护理,实际上这样并不符合主流价值观,而且,操作系统本身资源有限,这样无限生产不顾护理,操作系统也会吃不消的。
2、父进程对子进程的状态收集等是通过pcntl_wait()和pcntl_waitpid()等完成的。
演示并说明孤儿进程的出现,并演示孤儿进程被init进程收养:
<?php
$pid = pcntl_fork();
if( $pid > 0 ){
// 显示父进程的进程ID,这个函数可以是getmypid(),也可以用posix_getpid()
echo "Father PID:".getmypid().PHP_EOL;
// 让父进程停止两秒钟,在这两秒内,子进程的父进程ID还是这个父进程
sleep( 2 );
} else if( 0 == $pid ) {
// 让子进程循环10次,每次睡眠1s,然后每秒钟获取一次子进程的父进程进程ID
for( $i = 1; $i <= 10; $i++ ){
sleep( 1 );
// posix_getppid()函数的作用就是获取当前进程的父进程进程ID
echo posix_getppid().PHP_EOL;
}
} else {
echo "fork error.".PHP_EOL;
}
运行结果如下图:
可以看到,前两秒内,子进程的父进程进程ID为4129,但是从第三秒开始,由于父进程已经提前退出了,子进程变成孤儿进程,所以init进程收养了子进程,所以子进程的父进程进程ID变成了1。
演示并说明僵尸进程的出现,并演示僵尸进程的危害:
<?php
$pid = pcntl_fork();
if( $pid > 0 ){
// 下面这个函数可以更改php进程的名称
cli_set_process_title('php father process');
// 让主进程休息60秒钟
sleep(60);
} else if( 0 == $pid ) {
cli_set_process_title('php child process');
// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
sleep(10);
} else {
exit('fork error.'.PHP_EOL);
}
运行结果如下图:
可以看到,前两秒内,子进程的父进程进程ID为4129,但是从第三秒开始,由于父进程已经提前退出了,子进程变成孤儿进程,所以init进程收养了子进程,所以子进程的父进程进程ID变成了1。
演示并说明僵尸进程的出现,并演示僵尸进程的危害:
<?php
$pid = pcntl_fork();
if( $pid > 0 ){
// 下面这个函数可以更改php进程的名称
cli_set_process_title('php father process');
// 让主进程休息60秒钟
sleep(60);
} else if( 0 == $pid ) {
cli_set_process_title('php child process');
// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
sleep(10);
} else {
exit('fork error.'.PHP_EOL);
}
运行结果如下图:
通过执行ps -aux命令可以看到,当程序在前十秒内运行的时候,php child process的状态列为[S+],然而在十秒钟过后,这个状态变成了[Z+],也就是变成了危害系统的僵尸进程。
那么,问题来了?如何避免僵尸进程呢?PHP通过pcntl_wait()和pcntl_waitpid()两个函数来帮我们解决这个问题。了解Linux系统编程的应该知道,看名字就知道这其实就是PHP把C语言中的wait()和waitpid()包装了一下。
通过代码演示pcntl_wait()来避免僵尸进程,在开始之前先简单普及一下pcntl_wait()的相关内容:这个函数的作用就是 “ 等待或者返回子进程的状态 ”,当父进程执行了该函数后,就会阻塞挂起等待子进程的状态一直等到子进程已经由于某种原因退出或者终止。换句话说就是如果子进程还没结束,那么父进程就会一直等等等,如果子进程已经结束,那么父进程就会立刻得到子进程状态。这个函数返回退出的子进程的进程ID或者失败返回-1。
我们将第二个案例中代码修改一下:
<?php
$pid = pcntl_fork();
if( $pid > 0 ){
// 下面这个函数可以更改php进程的名称
cli_set_process_title('php father process'); // 返回$wait_result,就是子进程的进程号,如果子进程已经是僵尸进程则为0
// 子进程状态则保存在了$status参数中,可以通过pcntl_wexitstatus()等一系列函数来查看$status的状态信息是什么
$wait_result = pcntl_wait( $status );
print_r( $wait_result );
print_r( $status ); // 让主进程休息60秒钟
sleep(60);
} else if( 0 == $pid ) {
cli_set_process_title('php child process');
// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
sleep(10);
} else {
exit('fork error.'.PHP_EOL);
}
将文件保存为wait.php,然后php wait.php,在另外一个终端中通过ps -aux查看,可以看到在前十秒内,php child process是[S+]状态,然后十秒钟过后进程消失了,也就是被父进程回收了,没有变成僵尸进程。
但是,pcntl_wait()有个很大的问题,就是阻塞。父进程只能挂起等待子进程结束或终止,在此期间父进程什么都不能做,这并不符合多快好省原则,所以pcntl_waitpid()闪亮登场。pcntl_waitpid( Misplaced &status, $option = 0 )的第三个参数如果设置为WNOHANG,那么父进程不会阻塞一直等待到有子进程退出或终止,否则将会和pcntl_wait()的表现类似。
修改第三个案例的代码,但是,我们并不添加WNOHANG,演示说明pcntl_waitpid()功能:
<?php
$pid = pcntl_fork();
if( $pid > 0 ){
// 下面这个函数可以更改php进程的名称
cli_set_process_title('php father process'); // 返回值保存在$wait_result中
// $pid参数表示 子进程的进程ID
// 子进程状态则保存在了参数$status中
// 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
$wait_result = pcntl_waitpid( $pid, $status );
var_dump( $wait_result );
var_dump( $status ); // 让主进程休息60秒钟
sleep(60); } else if( 0 == $pid ) {
cli_set_process_title('php child process');
// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
sleep(10);
} else {
exit('fork error.'.PHP_EOL);
}
下面是运行结果,一个执行php程序的终端窗口,另一个是ps -aux终端窗口。实际上可以看到主进程是被阻塞的,一直到第十秒子进程退出了,父进程不再阻塞:
那么我们修改第四段代码,添加第三个参数WNOHANG,代码如下:
<?php
$pid = pcntl_fork();
if( $pid > 0 ){
// 下面这个函数可以更改php进程的名称
cli_set_process_title('php father process'); // 返回值保存在$wait_result中
// $pid参数表示 子进程的进程ID
// 子进程状态则保存在了参数$status中
// 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
$wait_result = pcntl_waitpid( $pid, $status, WNOHANG );
var_dump( $wait_result );
var_dump( $status );
echo "不阻塞,运行到这里".PHP_EOL; // 让主进程休息60秒钟
sleep(60); } else if( 0 == $pid ) {
cli_set_process_title('php child process');
// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
sleep(10);
} else {
exit('fork error.'.PHP_EOL);
}
运行结果,一个执行php程序的终端窗口,另一个是ps -aux终端窗口。实际上可以看到主进程是被阻塞的,一直到第十秒子进程退出了,父进程不再阻塞:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
问题出现了,竟然php child process进程状态竟然变成了[Z+],这是怎么搞得?回头分析一下代码:
我们看到子进程是睡眠了十秒钟,而父进程在执行pcntl_waitpid()之前没有任何睡眠且本身不再阻塞,所以,主进程自己先执行下去了,而子进程在足足十秒钟后才结束,进程状态自然无法得到回收。如果我们将代码修改一下,就是在主进程的pcntl_waitpid()前睡眠15秒钟,这样就可以回收子进程了。但是即便这样修改,细心想的话还是会有个问题,那就是在子进程结束后,在父进程执行pcntl_waitpid()回收前,有五秒钟的时间差,在这个时间差内,php child process也将会是僵尸进程。那么,pcntl_waitpid()如何正确使用啊?这样用,看起来毕竟不太科学。
那么,是时候引入信号学了!
PHP进程间通信的另外一个手段就是通过 信号 来在进程间传递信息。信号是一种系统调用。通常我们用的kill命令就是发送某个信号给某个进程的。具体有哪些信号可以在liunx/mac中运行kill -l
查看。
php的一些信号:
SIGHUP 终止进程 终端线路挂断 SIGINT 终止进程 中断进程 SIGQUIT 建立CORE文件终止进程,并且生成core文件 SIGILL 建立CORE文件 非法指令 SIGTRAP 建立CORE文件 跟踪自陷 SIGBUS 建立CORE文件 总线错误 SIGSEGV 建立CORE文件 段非法错误 SIGFPE 建立CORE文件 浮点异常 SIGIOT 建立CORE文件 执行I/O自陷 SIGKILL 终止进程 杀死进程 SIGPIPE 终止进程 向一个没有读进程的管道写数据 SIGALARM 终止进程 计时器到时 SIGTERM 终止进程 软件终止信号 SIGSTOP 停止进程 非终端来的停止信号 SIGTSTP 停止进程 终端来的停止信号 SIGCONT 忽略信号 继续执行一个停止的进程 SIGURG 忽略信号 I/O紧急信号 SIGIO 忽略信号 描述符上可以进行I/O SIGCHLD 忽略信号 当子进程停止或退出时通知父进程 SIGTTOU 停止进程 后台进程写终端 SIGTTIN 停止进程 后台进程读终端 SIGXGPU 终止进程 CPU时限超时 SIGXFSZ 终止进程 文件长度过长 SIGWINCH 忽略信号 窗口大小发生变化 SIGPROF 终止进程 统计分布图用计时器到时 SIGUSR1 终止进程 用户定义信号1 SIGUSR2 终止进程 用户定义信号2 SIGVTALRM 终止进程 虚拟计时器到时
下面来看一个例子。启动3个子进程,运行,父进程等待5秒钟,向子进程发送sigint信号。子进程捕获信号,调用信号处理函数处理。
<?php
/**
* author: NickBai
* createTime: 2016/12/5 0005 下午 3:01
*/
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n"; // 定义一个信号处理函数
function sighandler($signo) {
if( $signo == SIGINT ){
$pid = getmypid();
exit("{$pid} progress,oh no ,I'm killed!\n");
}
} //PHP < 5.3 使用
//配合pcntl_signal使用,表示每执行一条低级指令,就检查一次信号,如果检测到注册的信号,就调用其信号处理器。
//declare(ticks=1); pcntl_signal( SIGINT, 'sighandler'); //注册信号处理函数
$childList = []; for( $i = 0; $i < 3; $i++ ){
$pid = pcntl_fork();
if( $pid == 0 ){
while( true ){
//PHP >= 5.3
//调用已安装的信号处理器
//必须在循环里调用,为了检测是否有新的信号等待dispatching。
pcntl_signal_dispatch(); echo "i am child " . getmypid() . " and i am running ! \n";
$sec = rand(1,2);
sleep($sec);
} }else if( $pid == -1 ){
exit("fork fail!" . PHP_EOL);
}else{
$childList[$pid] = 1;
}
} sleep(5);
foreach( $childList as $key=>$vo ){
posix_kill( $key, SIGINT ); //触发SIGINIT信号
}
sleep(2); echo "($parentPid)parent is end " . PHP_EOL;
结果如下:
信号详细讲解可参考链接:https://www.cnblogs.com/martini-d/p/9711590.html
PHP多进程的实际处理的更多相关文章
- Python中的多进程与多线程(一)
一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...
- 取代SharedPreferences的多进程解决方案
Android的SharedPreferences用来存储一些键值对, 但是却不支持跨进程使用. 跨进程来用的话, 当然是放在数据库更可靠啦, 本文主要是给作者的新库PreferencesProvid ...
- python 多进程使用总结
python中的多进程主要使用到 multiprocessing 这个库.这个库在使用 multiprocessing.Manager().Queue时会出问题,建议大家升级到高版本python,如2 ...
- Nginx深入详解之多进程网络模型
一.进程模型 Nginx之所以为广大码农喜爱,除了其高性能外,还有其优雅的系统架构.与Memcached的经典多线程模型相比,Nginx是经典的多进程模型.Nginx启动后以daemon ...
- Python的多线程(threading)与多进程(multiprocessing )
进程:程序的一次执行(程序载入内存,系统分配资源运行).每个进程有自己的内存空间,数据栈等,进程之间可以进行通讯,但是不能共享信息. 线程:所有的线程运行在同一个进程中,共享相同的运行环境.每个独立的 ...
- 进击的Python【第十章】:Python的socket高级应用(多进程,协程与异步)
Python的socket高级应用(多进程,协程与异步)
- PHP的pcntl多进程
PHP使用PCNTL系列的函数也能做到多进程处理一个事务.比如我需要从数据库中获取80w条的数据,再做一系列后续的处理,这个时候,用单进程?你可以等到明年今天了...所以应该使用pcntl函数了. 假 ...
- 初探PHP多进程
h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h ...
- gdb进程调试,多进程调试
1.单进程的调试 常规的通过gdb cmd这种方式开启调试,特别说明的是通过attach的方法附加到一个指定的进程上去进行调试,这种方法适合于调试一个已经运行的进程,具体用法: gdb -p [pi ...
- python高级之多进程
python高级之多进程 本节内容 多进程概念 Process类 进程间通讯 进程同步 进程池 1.多进程概念 multiprocessing is a package that supports s ...
随机推荐
- java截取2个指定字符之间的字符串
/** * 截取字符串str中指定字符 strStart.strEnd之间的字符串 * * @param string * @param str1 * @param str2 * @return */ ...
- Overture里镲片的使用
在我们使用Overture进行作曲编曲时,往往会增添很多乐器设备来使我们的乐器更丰富,今天我们来一起看看Overture里镲片怎么使用以及它在Overture的什么位置呢? 镲片,是一种乐器,通常指爵 ...
- Lesson 01-Linux安装及基础命令
.Linux安装(略)2.基础命令 cd 切换目录 /home 切换到home目录 . 代表当前目录 .. 代表切换到当前目录的上级目录 ~ 代表切换到用户家目录 空 代表切换到用户家目录 - 代表切 ...
- python读Excel
import xlrd def open_excel(fileName="TransInfo.xlsx"): try: fileHandler = xlrd.open_workbo ...
- python小游戏
import time,random # 需要的数据和变量放在开头player_list = ['[狂血战士]','[森林箭手]','[光明骑士]','[独行剑客]','[格斗大师]','[枪弹专家] ...
- 函数嵌套定义,闭包及闭包的应用场景,装饰器,global.nonlocal关键字
函数的嵌套定义 在一个函数的内部定义另一个函数 为什么要有函数的嵌套定义: 1)函数fn2想直接使用fn1函数的局部变量,可以将fn2直接定义到fn1的内部,这样fn2就可以直接访问fn1的变凉了 2 ...
- YY的GCD
YY的GCD 给出T个询问,询问\(\sum_{i=1}^N\sum_{j=1}^M(gcd(i,j)\in prime)\),T = 10000,N, M <= 10000000. 解 显然质 ...
- 【转载】BlockingQueue
前言: 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便 ...
- SpringBoot使用日志
1.日志框架 日志门面 日志实现 JCL.SLF4J.jboss-logging Log4j.JUL.Log4j2.Logback 日志门面:SLF4J 日志实现:Logback SpringBoot ...
- .zip压缩版MySql的安装( )
Mysql解压缩版下载安装过程 1.进入https://www.mysql.com/downloads/官网进行mysql的下载 找到downloads首页最下方MySQL Community Edi ...