本篇文章跟大家分享的內容是PHP實現系統程式設計之多進程程式設計介紹及孤兒進程、殭屍進程,有著一定的參考價值,有需要的朋友可以參考一下
多進程程式設計也是系統程式設計的重要方面,但PHP程式設計師通常不需要關心多進程的問題,因為web伺服器或PHP-FPM已經幫我們管理好進程方面的問題了,但是如果我們想要用PHP來開發CLI程序,多進程編程是不可或缺的基本技術。
PHP中關於進程控制的方法主要使用到PCNTL(Process Control)擴充功能, 所以,在進行多進程程式設計之前,首先要確保你的PHP已經安裝了最新的PCNTL擴展,可以輸入php -m指令來查看目前已安裝的擴充功能:
#PCNTL 函数
pcntl_alarm — 为进程设置一个alarm闹钟信号
pcntl_errno — 别名 pcntl_get_last_error
pcntl_exec — 在当前进程空间执行指定程序
pcntl_fork — 在当前进程当前位置产生分支(子进程)。
pcntl_get_last_error — Retrieve the error number set by the last pcntl function which failed
pcntl_getpriority — 获取任意进程的优先级
pcntl_setpriority — 修改任意进程的优先级
pcntl_signal_dispatch — 调用等待信号的处理器
pcntl_signal_get_handler — Get the current handler for specified signal
pcntl_signal — 安装一个信号处理器
pcntl_sigprocmask — 设置或检索阻塞信号
pcntl_sigtimedwait — 带超时机制的信号等待
pcntl_sigwaitinfo — 等待信号
pcntl_strerror — Retrieve the system error message associated with the given errno
pcntl_wait — 等待或返回fork的子进程状态
pcntl_waitpid — 等待或返回fork的子进程状态
pcntl_wexitstatus — 返回一个中断的子进程的返回代码
pcntl_wifexited — 检查状态代码是否代表一个正常的退出。
pcntl_wifsignaled — 检查子进程状态码是否代表由于某个信号而中断
pcntl_wifstopped — 检查子进程当前是否已经停止
pcntl_wstopsig — 返回导致子进程停止的信号
pcntl_wtermsig — 返回导致子进程中断的信号
pcntl_fork
—
在目前進程目前位置產生分支(子進程) 。譯註:fork是創建了一個子進程,父進程和子進程
都從fork的位置開始向下繼續執行,不同的是父進程執行過程中,得到的fork回傳值為子進程號,而子進程得到的是0。
fork出的子進程幾近於完全的複製了父進程,父子進程共享程式碼段,雖然父子進程的資料段、堆、棧是相互獨立的,但在一開始,子進程完全複製了父進程的這些數據,但之後的修改互不影響。
#
int pcntl_fork ( void )
<?php for($i = 0; $i < 5; $i++) { $pid = pcntl_fork(); //创建子进程,子进程也是从这里开始执行。 if ($pid == 0) { break; //由于子进程也会执行循环的代码,所以让子进程退出循环,否则子进程又会创建自己的子进程。 } } sleep($i); //第一个创建的子进程将睡眠0秒,第二个将睡眠1s,依次类推...主进程会睡眠5秒 if ($i < 5) { exit("第 " . ($i+1) . " 个子进程退出..." . time() . PHP_EOL); } else { exit("父进程退出..." . time() . PHP_EOL); }
##建立5個子進程碼示範:
[root@localhost process]# php process.php 第 1 个子进程退出...1503322773 第 2 个子进程退出...1503322774 第 3 个子进程退出...1503322775 第 4 个子进程退出...1503322776 第 5 个子进程退出...1503322777 父进程退出...1503322778
執行結果:
##
<?php for($i = 0; $i < 5; $i++) { $pid = pcntl_fork(); if ($pid == 0) { break; //由于子进程也会执行循环的代码,所以让子进程退出循环 } } sleep($i); //第一个创建的子进程将睡眠0秒,第二个将睡眠1s,依次类推...主进程会睡眠5秒 /* if ($i < 5) { exit("第 " . ($i+1) . " 个子进程退出..." . time() . PHP_EOL); } else { exit("父进程退出..." . time() . PHP_EOL); } */ while(1) { sleep(1); //执行死循环不退出 }
fork是創建了一個子進程,父進程和子進程都從fork的位置開始向下繼續執行,不同的是父進程執行過程中,得到的fork回傳值為子進程號,而子進程得到的是0”
把上面的程式碼稍作修改,不讓行程退出,然後利用ps指令查看系統狀態:
[root@localhost ~]# ps -ef | grep php
root 3670 3609 0 21:54 pts/0 00:00:00 php process.php
root 3671 3670 0 21:54 pts/0 00:00:00 php process.php
root 3672 3670 0 21:54 pts/0 00:00:00 php process.php
root 3673 3670 0 21:54 pts/0 00:00:00 php process.php
root 3674 3670 0 21:54 pts/0 00:00:00 php process.php
root 3675 3670 0 21:54 pts/0 00:00:00 php process.php
root 3677 3646 0 21:54 pts/1 00:00:00 grep php
執行後輸入ps -ef | grep php 檢視系統程序
##
<?php $ppid = posix_getpid(); //记录父进程的进程号 for($i = 0; $i < 5; $i++) { $pid = pcntl_fork(); if ($pid == 0) { break; //由于子进程也会执行循环的代码,所以让子进程退出循环 } } if ($ppid == posix_getpid()) { //父进程 while(1) { sleep(1); } } else { //子进程 for($i = 0; $i < 100; $i ++) { echo "子进程" . posix_getpid() . " 循环 $i ...\n"; sleep(1); } }
[root@localhost process]# php process.php 子进程6677 循环 0 ... 子进程6676 循环 0 ... 子进程6678 循环 0 ... 子进程6680 循环 0 ... 子进程6679 循环 0 ... 子进程6677 循环 1 ... 子进程6676 循环 1 ... 子进程6678 循环 1 ... 子进程6680 循环 1 ... 子进程6679 循环 1 ... 子进程6677 循环 2 ... 子进程6676 循环 2 ... 子进程6678 循环 2 ... 子进程6680 循环 2 ...###### ###
void pcntl_exec ( string $path [, array $args [, array $envs ]] )#########其實上面的程式父子進程還是執行了相同的程式碼,只是進入的if分支不一樣,而pcntl_exec則可以讓子進程完全脫離父進程的影響,去執行新的程序。 ##################
pcntl_exec — 在当前进程空间执行指定程序
void pcntl_exec ( string $path [, array $args [, array $envs ]] )
path
path必须时可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)。 更多的信息请查看您系统的execve(2)手册。
args
args是一个要传递给程序的参数的字符串数组。
envs
envs是一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格式的,key代表要传递的环境变量的名称,value代表该环境变量值。
注意该方法的返回值比较特殊:当发生错误时返回 FALSE ,没有错误时没有返回,因为pcntl_exec调用成功,子进程就去运行新的程序 从父进程继承的代码段、数据段、堆、栈等信息全部被替换成新的,此时的pcntl_exec函数调用栈已经不存在了,所以也就没有返回了。代码示例:
<?php for($i = 0; $i < 3; $i++) { $pid = pcntl_fork(); if($pid == 0) { echo "子进程pid = " . posix_getpid() . PHP_EOL; $ret = pcntl_exec('/bin/ls'); //执行 ls 命令, 此处调用成功子进程将不会再回来执行下面的任何代码 var_dump($ret); // 此处的代码不会再执行 } } sleep(5); //睡眠5秒以确保子进程执行完毕,原因后面会说 exit( "主进程退出...\n");
运行结果:
[root@localhost process]# php pcntl_exec.php 子进程pid = 6728 子进程pid = 6729 子进程pid = 6727 pcntl_exec.php process.php pcntl_exec.php process.php pcntl_exec.php process.php 主进程退出... [root@localhost process]# ls pcntl_exec.php process.php
以上就是对PHP多进程开发的简单介绍,对于子进程不同的存续状态,引出孤儿进程和僵尸进程的概念,在linux系统中,init进程(1号进程)是所有进程的祖先,其他进程要么是该进程的子进程,要么是子进程的子进程,子进程的子进程的子进程...,linux系统中可以用 pstree 命令查看进程树结构:
在多进程程序中,如果父进程先于子进程退出,那么子进程将会被init进程收养,成为init进程的子进程,这种进程被称为孤儿进程,我们可以把上面的代码稍作修改来演示这种情况:
<?php $ppid = posix_getpid(); //记录父进程的进程号 for($i = 0; $i < 5; $i++) { $pid = pcntl_fork(); if ($pid == 0) { break; //由于子进程也会执行循环的代码,所以让子进程退出循环 } } if ($ppid == posix_getpid()) { //父进程直接退出,它的子进程都会成为孤儿进程 exit(0); } else { //子进程 for($i = 0; $i < 100; $i ++) { echo "子进程" . posix_getpid() . " 循环 $i ...\n"; sleep(1); } }
运行该程序,然后查看进程状态:
[root@localhost ~]# ps -ef | grep php root 2903 1 0 12:09 pts/0 00:00:00 php pcntl.fork.php root 2904 1 0 12:09 pts/0 00:00:00 php pcntl.fork.php root 2905 1 0 12:09 pts/0 00:00:00 php pcntl.fork.php root 2906 1 0 12:09 pts/0 00:00:00 php pcntl.fork.php root 2907 1 0 12:09 pts/0 00:00:00 php pcntl.fork.php root 2935 2912 0 12:10 pts/1 00:00:00 grep php
可以看到五个子进程的父进程号都是1了,并且这时控制台不再被程序占用,子进程转到了后台运行,这种孤儿进程被init进程收养的机制是实现后面将要介绍的守护进程的必要条件之一。
子进程还有一种状态叫僵尸进程,子进程结束时并不是完全退出,内核进程表中仍旧保有该进程的记录,这样做的目的是能够让父进程可以得知子进程的退出状态,以及子进程是自杀(调用exit或代码执行完毕)还是他杀(被信号终止),父进程可以调用pcntl_wait 或 pcntl_waitpid 方法来回收子进程(收尸),释放子进程占用的所有资源,并获得子进程的退出状态,如果父进程不做回收,则僵尸进程一直存在,如果这时父进程也退出了,则这些僵尸进程会被init进程接管并自动回收。
对于linux系统来说,一个长时间运行的多进程程序一定要回收子进程,因为系统的进程资源是有限的,僵尸进程会让系统的可用资源减少。
代码演示僵尸进程的产生:
<?php $ppid = posix_getpid(); //记录父进程的进程号 for($i = 0; $i < 5; $i++) { $pid = pcntl_fork(); if ($pid == 0) { break; //由于子进程也会执行循环的代码,所以让子进程退出循环 } } if ($ppid == posix_getpid()) { //父进程不退出,也不回收子进程 while(1) { sleep(1); } } else { //子进程退出,会成为僵尸进程 exit("子进程退出 $ppid ...\n"); }
运行之后查看进程状态:
[root@localhost ~]# ps -ef | grep php root 2971 2864 0 14:13 pts/0 00:00:00 php pcntl.fork.php root 2972 2971 0 14:13 pts/0 00:00:00 [php] <defunct> root 2973 2971 0 14:13 pts/0 00:00:00 [php] <defunct> root 2974 2971 0 14:13 pts/0 00:00:00 [php] <defunct> root 2975 2971 0 14:13 pts/0 00:00:00 [php] <defunct> root 2976 2971 0 14:13 pts/0 00:00:00 [php] <defunct> root 2978 2912 0 14:13 pts/1 00:00:00 grep php
僵尸进程会用 21eba13340b1d3a525d7480ca5bbd370(死者,死人) 来标识,除非我们结束父进程,否则这些僵尸进程会一直存在,也无法用kill命令来杀死。
PHP的pcntl扩展提供了两个回收子进程的方法供我们调用:
int pcntl_wait ( int &$status [, int $options = 0 ] ) int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )
pcntl_wait函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。 如果一个子进程在调用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将被释放。
关于wait在您系统上工作的详细规范请查看您系统的wait(2)手册。这个函数等同于以-1作为参数pid 的值并且没有options参数来调用pcntl_waitpid() 函数。
代码示例:
<?php $ppid = posix_getpid(); //记录父进程的进程号 for($i = 0; $i < 5; $i++) { $pid = pcntl_fork(); if ($pid == 0) { break; //由于子进程也会执行循环的代码,所以让子进程退出循环 } } if ($ppid == posix_getpid()) { //父进程循环回收收子进程 while(($id = pcntl_wait($status)) > 0) //如果没有子进程退出, pcntl_wait 会一直阻塞 { echo "回收子进程:$id, 子进程退出状态值: $status...\n"; } exit("父进程退出 $id....\n"); //当子进程全部结束 pcntl_wait 返回-1 } else { //子进程退出,会成为僵尸进程 sleep($i); exit($i); }
运行结果:
[root@localhost php]# php pcntl.fork.php 回收子进程:3043, 子进程退出状态值: 0... 回收子进程:3044, 子进程退出状态值: 256... 回收子进程:3045, 子进程退出状态值: 512... 回收子进程:3046, 子进程退出状态值: 768... 回收子进程:3047, 子进程退出状态值: 1024... 父进程退出 -1....
这里只是对PHP多进程编程做了基本的介绍,后面会结合 信号、进程间通信以及守护进程 做更进一步的介绍,欢迎大家关注后续文章。
PHP是世界上最好的语言 That's all :)
相关推荐:
以上是PHP實作系統程式設計之 多進程程式設計介紹及孤兒流程、殭屍流程的詳細內容。更多資訊請關注PHP中文網其他相關文章!