实例讲解:学会这两个函数,你就能玩转多进程
本文通过6个例子,让你彻底搞懂如何fork子进程,和监听子进程是否结束。轻松搞定:让PHP创建多个子进程,高效处理大批量数据。
查看进程数有4个,其中1个父进程,3个子进程,说明已经成功创建了子进程。
执行程序打印结果如下
运行程序打印结果如下,可以看到父进程先结束,子进程按自己所需要的时间结束:
运行程序打印结果如下,可以看出第1个子进程只改变了自己的 $count 的值,没有改变父进程的,也没改变其它子进程的:
答案是:可以监听到。
答案是:可以监听到。
再次查看进程数,确定子进程已经被杀死了,只剩下3个进程。
创建子进程的作用
答:创建多个子进程,可以让多个子进程同时处理大量的数据,提高处理数据的效率,例如:发送10万个红包、给10万个用户发送开课提醒。<?php
class pcntl
{
/**
* 创建子进程的作用
* 答:创建多个子进程,可以让多个子进程同时处理大量的数据,提高处理数据的效率,例如:发送10万个红包、给10万个用户发送开课提醒
*/
public function demo_1()
{
$data = [[1, 2], [3, 4], [5, 6]];
foreach ($data as $i => $ids) {
$pid = pcntl_fork();
if ($pid == -1) {
echo "创建子进程失败 \$i = $i \n";
}
elseif ($pid) { //pid不为0则说明是父进程
echo "我是父进程 \$i = $i pid = $pid \n";
}
else { //新fork出来的子进程的pid为0
$ids = json_encode($ids);
echo " 我是子进程, \$i = $i pid = $pid \$ids = $ids \n";
//这里可以写你的业务代码,例如:给用户发送红包
sleep(10);
exit; //需要停止,要不然会执行后面的代码(包括继续foreach之后的循环),后面的代码是给父进程执行的
}
}
sleep(10);
echo "父进程结束了 \n";
}
$demo = new pcntl();
$demo->demo_5();
[root@YiluPHP ~]# ps -ef|grep pcntl
root 3438 2932 0 18:23 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 3924 3438 0 18:24 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 3925 3438 0 18:24 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 3926 3438 0 18:24 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 4309 27890 0 18:24 pts/0 00:00:00 grep pcntl
[root@YiluPHP ~]# /usr/local/php74/bin/php pcntl.php
我是父进程 $i = 0 pid = 3924
我是子进程, $i = 0 pid = 0 $ids = [1,2]
我是父进程 $i = 1 pid = 3925
我是子进程, $i = 1 pid = 0 $ids = [3,4]
我是父进程 $i = 2 pid = 3926
我是子进程, $i = 2 pid = 0 $ids = [5,6]
父进程结束了
如果父进程结束了,子进程会不会跟着强制结束?
答案是:子进程不会跟着结束,子进程会继续执行。
<?php
class pcntl
{
/**
* 如果父进程结束了,子进程会不会跟着强制结束
* 答案是:子进程不会跟着结束,子进程会继续执行
*/
public function demo_2()
{
$data = [[1, 2], [3, 4], [5, 6]];
foreach ($data as $i => $ids) {
$pid = pcntl_fork();
if ($pid == -1) {
echo "创建子进程失败 \$i = $i \n";
}
elseif ($pid) { //pid不为0则说明是父进程
echo "我是父进程 \$i = $i pid = $pid \n";
}
else { //新fork出来的子进程的pid为0
$ids = json_encode($ids);
echo " 我是子进程, \$i = $i pid = $pid \$ids = $ids \n";
//这里可以写你的业务代码,例如:给用户发送红包
sleep(3);
echo "\n再见,\$i = $i \n";
exit; //需要停止,要不然会执行后面的代码,后面的代码是给父进程执行的
}
}
sleep(1);
echo "\n父进程结束了 \n";
}
$demo = new pcntl();
$demo->demo_5();
[root@YiluPHP ~]# /usr/local/php74/bin/php pcntl.php
我是父进程 $i = 0 pid = 31430
我是子进程, $i = 0 pid = 0 $ids = [1,2]
我是父进程 $i = 1 pid = 31431
我是子进程, $i = 1 pid = 0 $ids = [3,4]
我是父进程 $i = 2 pid = 31432
父进程结束了
我是子进程, $i = 2 pid = 0 $ids = [5,6]
再见,$i = 0
再见,$i = 1
再见,$i = 2
子进程能不能改变父进程的值,进而改变其它子进程的值?
答案是:不能
创建子进程时会拷贝一份当时已经存在的所有变量,之后各个子进程之间就像不同的http请求一样,变量、类的静态变量都是互不影响的。
<?php
class pcntl
{
/**
* 子进程能不能改变父进程的值,进而改变其它子进程的值
* 答案是:不能
* 创建子进程时会拷贝一份当时已经存在的所有变量,之后各个子进程之间就像不同的http请求一样,变量、类的静态变量都是互不影响的
*/
public function demo_3()
{
$data = [[1, 2], [3, 4], [5, 6]];
$count = -1;
foreach ($data as $i => $ids) {
$pid = pcntl_fork();
if ($pid == -1) {
echo "创建子进程失败 \$i = $i \n";
}
elseif ($pid) { //pid不为0则说明是父进程
echo "我是父进程 \$i = $i pid = $pid \n";
}
else { //新fork出来的子进程的pid为0
$ids = json_encode($ids);
echo " 我是子进程, \$i = $i pid = $pid \$ids = $ids \n";
if ($i == 0) { //在第1个子进程中把 $count 的值设置为666
$count = 666;
}
//这里可以写你的业务代码,例如:给用户发送红包
sleep($i+1);
echo "\n再见,\$i = $i \$count = $count \n";
exit; //需要停止,要不然会执行后面的代码,后面的代码是给父进程执行的
}
}
sleep(6);
echo "\n父进程结束了 \$count = $count \n";
}
$demo = new pcntl();
$demo->demo_5();
[root@YiluPHP ~]# /usr/local/php74/bin/php pcntl.php
我是父进程 $i = 0 pid = 7875
我是子进程, $i = 0 pid = 0 $ids = [1,2]
我是父进程 $i = 1 pid = 7876
我是子进程, $i = 1 pid = 0 $ids = [3,4]
我是父进程 $i = 2 pid = 7877
我是子进程, $i = 2 pid = 0 $ids = [5,6]
再见,$i = 0 $count = 666
再见,$i = 2 $count = -1
再见,$i = 1 $count = -1
父进程结束了 $count = -1
父进程如何知道所有的子进程都已经结束?
答:使用 pcntl_waitpid 函数监听子进程的状态。
<?php
class pcntl
{
/**
* 父进程如何知道所有的子进程都已经结束
* 答:使用 pcntl_waitpid 函数监听子进程的状态
*/
public function demo_4()
{
$data = [[1, 2], [3, 4], [5, 6]];
$count = 0;
foreach ($data as $i => $ids) {
$count++; //加一个子进程数,最终肯定会将值加到4
$pid = pcntl_fork();
if ($pid == -1) {
echo "创建子进程失败 \$i = $i \n";
}
elseif ($pid) { //pid不为0则说明是父进程
echo "我是父进程 \$i = $i pid = $pid \n";
}
else { //新fork出来的子进程的pid为0
$ids = json_encode($ids);
echo " 我是子进程, \$i = $i pid = $pid \$ids = $ids \n";
sleep(rand(1, 3));
echo "\n再见,\$i = $i\n";
exit; //需要停止,要不然会执行后面的代码,后面的代码是给父进程执行的
}
}
//此处监听子进程的状态
while (($pid2 = pcntl_waitpid(-1, $status, WUNTRACED)) > 0) { //返回已经结束的子进程的pid
echo "\n子进程 $pid2 已结束 \n";
$count--;
}
echo "\n父进程结束了 \$count = $count \n";
}
$demo = new pcntl();
$demo->demo_5();
通过打印可以看到,只有所有的子进程结束之后,父进程才会结束,可以推断出:在整个过程中, $count的值加到4,然后再减到0。
[root@YiluPHP1 ~]# /usr/local/php74/bin/php pcntl.php
我是父进程 $i = 0 pid = 17940
我是子进程, $i = 0 pid = 0 $ids = [1,2]
我是父进程 $i = 1 pid = 17941
我是子进程, $i = 1 pid = 0 $ids = [3,4]
我是父进程 $i = 2 pid = 17942
我是子进程, $i = 2 pid = 0 $ids = [5,6]
再见,$i = 1
再见,$i = 0
再见,$i = 2
子进程 17941 已结束
子进程 17940 已结束
子进程 17942 已结束
父进程结束了 $count = 0
当子进程的程序因致命错误中断了,使用 pcntl_waitpid 函数能监听到子进程已结束吗?
<?php
class pcntl
{
/**
* 当子进程的程序因致命错误中断了,使用 pcntl_waitpid 函数能监听到子进程已结束吗
* 答案是:可以监听到
*/
public function demo_5()
{
$data = [[1, 2], [3, 4], [5, 6]];
$count = 0;
foreach ($data as $i => $ids) {
$count++; //加一个子进程数,最终肯定会将值加到4
$pid = pcntl_fork();
if ($pid == -1) {
echo "创建子进程失败 \$i = $i \n";
}
elseif ($pid) { //pid不为0则说明是父进程
echo "我是父进程 \$i = $i pid = $pid \n";
}
else { //新fork出来的子进程的pid为0
$ids = json_encode($ids);
echo " 我是子进程, \$i = $i pid = $pid \$ids = $ids \n";
if ($i == 2) { //只让第3个子进程意外中断
no_fun(); //调用一个不存在的函数,制造一个致命错误让程序中断
}
sleep(3);
echo "\n再见,\$i = $i\n";
exit; //需要停止,要不然会执行后面的代码,后面的代码是给父进程执行的
}
}
while (($pid2 = pcntl_waitpid(-1, $status, WUNTRACED)) > 0) { //返回已经结束的子进程的pid
echo "\n子进程 $pid2 已结束 \n";
$count--;
}
echo "\n父进程结束了 \$count = $count \n";
}
$demo = new pcntl();
$demo->demo_5();
通过打印可以看到,第3个子进程提前结束了,它没有输出“再见”的字符,但是父进程也正常的结束了,$count 最终减到了0。
[root@YiluPHP1 ~]# /usr/local/php74/bin/php pcntl.php
我是父进程 $i = 0 pid = 17229
我是子进程, $i = 0 pid = 0 $ids = [1,2]
我是父进程 $i = 1 pid = 17230
我是子进程, $i = 1 pid = 0 $ids = [3,4]
我是父进程 $i = 2 pid = 17231
我是子进程, $i = 2 pid = 0 $ids = [5,6]
Fatal error: Uncaught Error: Call to undefined function no_fun() in pcntl.php on line 215
Error: Call to undefined function no_fun() in pcntl.php on line 215
Call Stack:
0.0022 350952 1. {main}() pcntl.php:0
0.0023 350992 2. pcntl->demo_5() pcntl.php:305
子进程 17231 已结束
再见,$i = 0
再见,$i = 1
子进程 17230 已结束
子进程 17229 已结束
父进程结束了 $count = 0
如果手工杀掉子进程,使用 pcntl_waitpid 函数能监听到子进程已结束吗?
<?php
class pcntl
{
/**
* 如果手工杀掉子进程,使用 pcntl_waitpid 函数能监听到子进程已结束吗
* 答案是:可以监听到
*/
public function demo_6()
{
$data = [[1, 2], [3, 4], [5, 6]];
$count = 0;
foreach ($data as $i => $ids) {
$count++; //加一个子进程数,最终肯定会将值加到4
$pid = pcntl_fork();
if ($pid == -1) {
echo "创建子进程失败 \$i = $i \n";
}
elseif ($pid) { //pid不为0则说明是父进程
echo "我是父进程 \$i = $i pid = $pid \n";
}
else { //新fork出来的子进程的pid为0
$ids = json_encode($ids);
echo " 我是子进程, \$i = $i pid = $pid \$ids = $ids \n";
sleep(30);
echo "\n再见,\$i = $i\n";
exit; //需要停止,要不然会执行后面的代码,后面的代码是给父进程执行的
}
}
while (($pid2 = pcntl_waitpid(-1, $status, WUNTRACED)) > 0) { //返回已经结束的子进程的pid
echo "\n子进程 $pid2 已结束 \n";
$count--;
}
echo "\n父进程结束了 \$count = $count \n";
}
$demo = new pcntl();
$demo->demo_5();
先查看正在运行的进程数,有4个进程。
[root@YiluPHP1 ~]# ps -ef|grep pcntl
root 29784 2932 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 30258 29784 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 30259 29784 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 30260 29784 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 30463 27890 0 19:31 pts/0 00:00:00 grep pcntl
结束掉pid为30259的子进程。
[root@YiluPHP1 ~]# kill 30259
[root@YiluPHP1 ~]# ps -ef|grep pcntl
root 29784 2932 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 30258 29784 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 30260 29784 0 19:31 pts/1 00:00:00 /usr/local/php74/bin/php pcntl.php
root 31411 27890 0 19:32 pts/0 00:00:00 grep pcntl
通过打印结果可以看到,父进程能捕捉到手工杀掉的子进程状态,父进程可以正常结束:
我是父进程 $i = 0 pid = 30258
我是子进程, $i = 0 pid = 0 $ids = [1,2]
我是父进程 $i = 1 pid = 30259
我是子进程, $i = 1 pid = 0 $ids = [3,4]
我是父进程 $i = 2 pid = 30260
我是子进程, $i = 2 pid = 0 $ids = [5,6]
子进程 30259 已结束
再见,$i = 0
再见,$i = 2
子进程 30258 已结束
子进程 30260 已结束
父进程结束了 $count = 0
最后大家想一个问题:这里生成了3个子进程,如果其中1个子进程结束了,如何补加1个子进程呢?这样就可以始终保持有3个子进程在运行。