短文2:用 pcntl_fock 函数浅谈多进程怎么回事

介绍

pcntlphp进程控制扩展,详见 php 手册。其中 pcntl_fork() 是创建进程函数。

workman 就是使用的这个扩展

ps -ef | grep 或者 pstree -apn | grep 想必一般都用过,打印出来的是这样的:

ps -ef

上图中一个 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_fock 函数浅谈多进程怎么回事

也就是说 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 的进程结构出来了

用 pcntl_fock 函数浅谈多进程怎么回事

用 pcntl_fock 函数浅谈多进程怎么回事

利用 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;

执行下

用 pcntl_fock 函数浅谈多进程怎么回事

可以看到先打印出来的 1 是父进程输出的,后面的 2 是子进程输出的。

本作品采用《CC 协议》,转载必须注明作者和本文链接
welcome come back
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
95
粉丝
24
喜欢
156
收藏
348
排名:324
访问:2.9 万
私信
所有博文
社区赞助商