PHP多进程学习(一)__来初步了解一下PHP多进程及简单demo
php是一门单进程弱类型的语言,PHP处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,多进程的作用优点大家可以去网上了解,PHP实现多进程在实际项目中意义也是不容小觑的。比如:日常任务中,有时需要通过php脚本执行一些日志分析,队列处理等任务,当数据量比较大时,可以使用多进程来处理。
要实现PHP的多进程,需要用到函数pcntl_fork,那么就需要开启扩展 pcntl和 posix,在上一篇文章已经有安装方法。
入门须知
- 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
- 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
- 僵尸进程危害:如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。
- 已经产生的僵尸进程,解决方法:kill掉父进程,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源。
鸟哥语录(多进程优点)http://www.laruence.com/2009/06/11/930.html
- 使用多进程, 子进程结束以后, 内核会负责回收资源
- 使用多进程,子进程异常退出不会导致整个进程Thread退出. 父进程还有机会重建流程.
- 一个常驻主进程, 只负责任务分发, 逻辑更清楚.
切记!切记!切记!
- PHP多进程一般应用在PHP_CLI命令行中执行php脚本,不要在web访问时使用。
- 通过pcntl_XXX系列函数使用多进程功能。注意:pcntl_XXX只能运行在php CLI(命令行)环境下,在web服务器环境下,会出现无法预期的结果,请慎用!
- 鸟哥提醒:也就是说, 打消你在PHP Web开发中使用多进程的念头吧!
创建子进程(pcntl_fork)
pcntl_fork() — 在当前进程当前位置产生分支(子进程)。此函数创建了一个新的子进程后,子进程会继承父进程当前的上下文,和父进程一样从pcntl_fork()函数处继续向下执行,只是获取到的pcntl_fork()的返回值不同,我们便能从判断返回值来区分父进程和子进程,分配父进程和子进程去做不同的逻辑处理。
pcntl_fork()函数成功执行时会在父进程返回子进程的进程id(pid),因为系统的初始进程init进程的pid为1,后来产生进程的pid都会大于此进程,所以我们可以通过判断pcntl_fork()的返回值大于1来确实当前进程是父进程;
而在子进程中,此函数的返回值会是固定值0,我们也可以通过判断pcntl_fork()的返回值为0来确定子进程;
而pcntl_fork()函数在执行失败时,会在父进程返回-1,当然也不会有子进程产生。
简单demo
<?php $ppid = posix_getpid(); $pid = pcntl_fork(); if ($pid == -1) { echo 'fork子进程失败!'; } elseif ($pid > 0) { echo "我是父进程,我的进程id是{$ppid}."; echo "\r\n"; sleep(20); // 保持20秒,确保能被ps查到 }else{ $cpid = posix_getpid(); echo "我是{$ppid}的子进程,我的进程id是{$cpid}."; echo "\r\n"; sleep(20); // 保持20秒,确保能被ps查到 }
# php fork.php //centos下执行命令 我是父进程,我的进程id是7625. 我是7625的子进程,我的进程id是7626.
# ps aux | grep fork.php //centos下20秒内执行命令 root 7625 0.0 0.6 143892 6496 pts/1 S+ 03:27 0:00 php fork.php root 7626 0.0 0.4 143892 4252 pts/1 S+ 03:27 0:00 php fork.php root 7628 7.0 0.0 103268 860 pts/2 S+ 03:27 0:00 grep fork.php
posix_getpid()函数作用是:获取当前进程的pid;
进一步说明
上边的代码如果创建子进程成功的话,系统就有了2个进程,一个为父进程,一个为子进程,子进程的id号为$pid。在系统运行到$pid = pcntl_fork();时,在这个地方进行分支,父子进程各自开始运行各自的程序代码。代码的运行结果是父进程那块代码 和子进程的代码都走了,很奇怪吧,为什么一个elseif和else互斥的代码中,都输出了结果?其实是像上边所说的,代码在pcntl_fork时,一个父进程运行父进程那块代码,一个子进程运行了子进程那块代码。在代码结果上就显示了“我是父进程….”和”我是子进程…”。至于谁先谁后的问题,这得要看系统资源的分配了。
PHP多进程学习(二)__fork起多个子进程,父进程的阻塞与非阻塞
先简单来了解一下多进程 [来初步了解一下PHP多进程及简单demo]
php的多进程是不是可以无限制的fork子进程?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0
<?php //定义进程数量 define('FORK_NUMS', 5); //用于保存进程pid $pids = array(); //我们创建5个子进程 for ($i = 0; $i < FORK_NUMS; ++$i) { $pids[$i] = pcntl_fork(); if ($pids[$i] == -1) { die('fork error'); } else if ($pids[$i]) { //这里是父进程空间,也就是主进程 //我们的for循环第一次进入到这里时,pcntl_wait会挂起当前主进程,等待第一个子进程执行完毕退出 //注意for循环的代码是在主进程的,挂起主进程,相当于当前的for循环也阻塞在这里了 //第一个子进程退出后,然后再创建第二个子进程,到这里后又挂起,等待第二个子进程退出,继续创建第三个,等等。。 pcntl_wait($status); } else { //这里是子进程空间 echo "父进程ID: ", posix_getppid(), " 进程ID : ", posix_getpid(), " {$i} \r\n"; //我们让子进程等待3秒,再退出 sleep(3); exit; } }
阻塞与非阻塞案例
通过pcntl_fork来创建子进程,使用pcntl_wait和pcntl_waitpid来回收子进程。子进程退出后,父进程没有及时回收,就会产生僵尸进程。
[阻塞案例]
<?php define('FORK_NUMS', 5); $pids = array(); //创建5个子进程 for($i = 0; $i < FORK_NUMS; ++$i) { $pids[$i] = pcntl_fork(); if($pids[$i] == -1) { die('fork error'); } else if ($pids[$i]) { pcntl_wait($status); } else { echo getmypid() , " {$i} \r\n"; exit; } }
# php fork.php 52470 0 52471 1 52472 2 52473 3 52474 4
以上代码通过for循环fork出5个子进程,父进程会阻塞着等待子进程退出,然后创建下一个子进程。
———————BUG:创建多进程的目的,就是为了能够并行的处理任务,阻塞的方式并不是我们期待的结果。——————
[非阻塞案例]
<?php define('FORK_NUMS', 5); $pids = array(); //创建5个子进程 for($i = 0; $i < FORK_NUMS; ++$i) { $pids[$i] = pcntl_fork(); if($pids[$i] == -1) { die('fork error'); } else if ($pids[$i]) { pcntl_wait($status, WNOHANG); } else { echo getmypid() , " {$i} \r\n"; exit; } }
//反复执行几次,发现没有规律 [root@bogon default]# php fork.php 52645 0 52647 2 52646 1 52648 3 52649 4 [root@bogon default]# php fork.php 52660 0 52663 3 52664 4 52661 1 52662 2 [root@bogon default]# php fork.php 52681 0 52683 2 52685 4 52684 3 52682 1
我们可以通过设置pcntl_wait的第二个参数为WNOHANG来控制进程是否阻塞。该函数可以在没有子进程退出的情况下立刻跳出执行后续代码。
pcntl_wait等同于以pid为-1调用pcntl_waitpid函数。
pcntl_waitpid函数可以等待指定pid的进程。
本文章参考的https://www.cnblogs.com/jkko123/p/6294602.html