短文2:用 pcntl_fock 函数浅谈多进程怎么回事
介绍
pcntl
是 php
的 进程控制扩展
,详见 php 手册。其中 pcntl_fork()
是创建进程函数。
workman 就是使用的这个扩展
ps -ef | grep
或者 pstree -apn | grep
想必一般都用过,打印出来的是这样的:
上图中一个 master
进程下面并列两个分支,就是子进程了。
使用过 workman 的也很熟悉上面的结构,workman 底层是 php 写的,依赖于 php 扩展,其中 pcntl
就是其中之一很重要的依赖扩展,它的 master
worker
结构就是这个扩展实现的。
官方DEMO
回归正题,使用 pcntl_fork()
实现一下,fork
翻译为 “分叉”,fork 一个子进程 即创建一个子进程。
<?php
$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die('could not fork');
} else if ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}
原理分析
上方代码是官方示例代码,具体过程是执行到 pcntl_fork()
时 “拷贝” 一份原文件(代码是一模一样的哦)
也就是说 pcntl_fork();
的一霎那,之后的代码同时在两个进程里执行(包括给 $pid
赋值),所以 $pid
在两个进程里的赋值是不一样的,在父进程里返回的 $pid
大于 0,等于子进程的进程 id
,通常叫做 pid
(process id)。而子进程里返回的 $pid
为 0。
所以通过 if
判断,就区分开了哪个代码块里应该执行哪个角色的逻辑。master-worker
模型,一般master
负责管理子进程数量等、处理信号啥的,不处理业务逻辑,而 worker
进程,顾名思义,工作进程。
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// 父进程执行代码块
$master_pid = posix_getpid(); //获得当前进程的 pid
$worker_pids[] = $pid; // 收集子进程 pid
echo "master_pid:".$master_pid.EOL;
var_dump($worker_pids);
pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
echo PHP_EOL.'等待了10秒后..status:'.$status;
} else {
// 子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
// 干活
//为了不让进程过早退出 咱 sleep 一下
sleep(10);
}
类似于 nginx 的进程结构出来了
利用 for 循环批量创建多个子进程时的注意事项
如果把创建子进程的代码包裹起来封装成为一个方法用于批量创建,那么要小心,由于上面也提到了,在 fork 的一刹那,是将代码 “拷贝” 了一份,像下面的例子中如果不在子进程空间 exit
,那么 for 循环也会在子进程中执行
function forkWorker($worker_num)
{
for ($i=0; $i < $worker_num; $i++)
{
$pid = pcntl_fork();
if ($pid<0){
exit('fork fail!');
} else if ($pid>0){
//父进程空间
pcntl_wait($status);
} else {
//子进程空间
exit; //注意一定要exit,子进程会继续循环,成为嵌套子进程
}
}
}
pcntl_wait 的作用
pcntl_wait($status);
是防止子进程成为僵尸进程的函数,是阻塞的,类似监听。也就是当子进程执行完退出时,或者主动 exit()
时,父进程将捕获状态码赋予 $status
,并且清理子进程。
父进程和子进程是隔离的
最后一个点,前面提到:执行到 pcntl_fork()
时 “拷贝” 一份原文件。注意的一点是,子进程继承了父进程的变量和方法,但由于是两个进程(两个脚本), 在子进程中给变量重新赋值,不会影响父进程里的变量值,进程之间是隔离的。
<?php
$i = 1;
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// 父进程
echo '我是父进程:'.$i.PHP_EOL;
} else {
// 子进程赋值
$i = 2;
sleep(1);
echo '我是子进程:'.$i.PHP_EOL;
}
echo '两个进程都会执行:'.$i.PHP_EOL;
执行下
可以看到先打印出来的 1
是父进程输出的,后面的 2
是子进程输出的。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: