ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

如何使用PHP多进程开发

2021-01-13 11:57:18  阅读:191  来源: 互联网

标签:执行 pid 孤儿 dispatchInfo 开发 进程 pcntl PHP


1.使用多进程的一些场景

  重复且耗时的一些操作,例如 发邮件,处理文件,或者是某些批量处理独立个体的事情。例如博主本次用到的场景是批量同步实体信息的操作,每个账户的实体是独立的,量级比较大,且处理逻辑有较多的网络通讯消耗和数据库查询。导致脚本执行经常卡主。卡点主要是处理慢,且并发,机器负载高,导致进程能分到的时间片也不多。

  PHP 的多进程和 协程 并不是一样的概念,一个是基于进程,另一个是基于线程的。我们知道操作系统最小的调度单位是进程,一个进程可以包含至少一个线程。进程间是相互隔离的,进程间的通讯只能基于一些特殊的方式,如管道、共享内存、消息队列等方式,而线程是共享进程资源的,所以通讯不需要那么麻烦,但是线程安全问题却又是一个弊端,资源是共享的,导致大家都有权限去动它,就会容易出问题,所以互斥锁是必要的,导致多线程开发会比多进程开发会复杂一些,后面我写到关于swoole的一些文章时我们再进行讨论。

2.php 如何 使用多进程

我们知道,c 语言 是基于 fork 函数进行子进程的创建的。Linux 也是基于这个原理,所有的Linux进程(除0号进程)都是通过 0号进程 fork 而来,这个 0 号进程是所有进程的始祖。

首先你要确保,你的已经安装了 pecl (如果没有安装过拓展,可以查一下我之前的文章教学),且代码执行在 linux php-cli 模式下。

PHP 也是提供了类C 函数,我们这次学习需要使用的函数如下:

2.1 创建子进程

pcntl_fork ( void ) : int

成功时,在父进程执行线程内返回产生的子进程的PID,在子进程执行线程内返回0。失败时,在 父进程上下文返回-1,不会创建子进程,并且会引发一个PHP错误。

文档栗子:

<?php

$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
    //错误处理:创建子进程失败时返回-1.
     die('could not fork');
} else if ($pid) {
     //父进程会得到子进程号,所以这里是父进程执行的逻辑
     pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
     //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}

?>

大概讲解一下,这个函数是PHP用来创建子进程的,如果成功返回值是子进程的进程ID,如果返回 -1 ,则代表创建子进程失败,如果是返回 0 ,说明创建子进程成功,但是此时执行的是父进程的代码,进程间是独立的,我们没办法去控制哪个进程先执行。当 cpu 的时间片分给了它,它就会执行,除非我们使用阻塞的代码去强制让其指定顺序的执行。因此在这种情况我们可以使用 返回值来进行判断当前执行的是父进程代码还是子进程代码。

2.2 获取当前的进程ID

getmypid ( void ) : int

函数会直接返回当前代码执行进程的进程 ID

2.3 监听进程

pcntl_waitpid ( int $pid , int &$status [, int $options = 0 [, array &$rusage ]] ) : int

pcntl_waitpid()返回退出的子进程进程号,发生错误时返回-1,如果提供了 WNOHANG作为option(wait3可用的系统)并且没有可用子进程时返回0。

这个我们是用来监听子进程是否执行结束使用的,这里我们只是使用了其中一种方式,更多对该函数的使用可以自己研究。

2.4 写一个较为完整的栗子

<?php

namespace Test;

class Test
{
    /** @var int 默认是 10 个进程 */
    protected $processNums;

    /** @var int 子进程时间片 */
    const CHILD_RUN = 0;

    /** @var int 失败 */
    const FORK_ERROR = -1;


    public function __construct(int $processNums = 10)
    {
        $this->processNums = $processNums;
    }

    public function run()
    {

        for ($i = 1; $i <= $this->processNums; $i++) { // 创建指定的子进程个数,这里从 1 开始方便计数
            $pid = pcntl_fork();
            $this->dispatchInfo("当前进程:" . getmypid() . ' fork的子进程:' . $pid);
            switch ($pid) {
                case self::FORK_ERROR:
                    $this->dispatchInfo('子进程创建失败');
                    break;
                case self::CHILD_RUN:
                    //子进程
                    $this->dispatchInfo("当前子进程:" . getmypid() . ' ' . "  子进程执行任务");
                    // 下面写你要实现的方法
                    exit(0);//执行完就退出把!
                    break;
                default:
                    //父进程执行过程
                    $this->dispatchInfo("当前进程:" . getmypid() . ' ' . "   开启第{$i}个子进程{$pid}");
                    $childPids[] = $pid;
                    break;
            }
        }

        while (count($childPids) > 0) {
            foreach ($childPids as $key => $pid) {
                $res = pcntl_waitpid($pid, $status, WNOHANG);// 非阻塞模式,让所有子进程都能被监听
                if ($res == -1 || $res > 0) { //这里我也奇怪,官方文档的栗子也是这么判断子进程已经结束的
                    $this->dispatchInfo("   子进程{$pid}关闭...");
                    unset($childPids[$key]);//剔除已关闭的子进程
                }
            }

            if (count($childPids) == 0) { // 全都跑完了
                break;
            }
        }
        $this->dispatchInfo('全部任务结束,主进程退出');
    }

    protected function dispatchInfo(string $info)
    {
        echo date("Y-m-d H:i:s") . '  ' . $info . PHP_EOL;
    }

}

3.僵尸进程和孤儿进程的产生

  子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。并且通过这种方式去回收对应的资源。

这里我们解释一下这两种进程的产生以及危害。

3.1 孤儿进程

我们知道,每个用户态进程都是由它爸爸生出来的,如果爸爸死了,那它就变成了孤儿了。变成孤儿会有啥问题呢?

其实也没啥问题,因为会有人来领养它,有一个进程 叫 init进程(1号进程), 它是一个好人,专门领养孤儿进程,让这些孤儿可以寿终正寝。因此本质上来说,如果父进程被杀了,也不会对系统造成太大的影响。

3.2 僵尸进程

僵尸,是一个恐怖的存在,死不了啊,不死就占地方,地球就这么大,又没有无限的容量。什么情况下会产生僵尸进程呢?

就是死神罢工了,这里的死神其实就是父进程。子进程执行完后,会留下一个进程标识符,等待父进程去回收,如果父进程没有回收,那么这个进程标识符(进程ID)就会一直被占用,就好比说厕所上完了不开门,不给别人去用。

为了区分开孤儿进程和僵尸进程的产生原因,我们可以这么想,子进程在 exit 之后,父进程应该主动的去接受信号释放子进程的进程标志符,也就是前面的 pcntl_waitpid 函数,如果我们通过父进程创建子进程,却没有主动去释放的话,那么僵尸进程必然产生。此时父进程和子进程都可以正常的结束,但是子进程的进程标识符无法被系统释放。

然后我们再想想另一种情况,父进程被 kill 掉了,会发生什么情况,我们前面说没有父进程,子进程就变成了孤儿进程,会被接管。假设父进程挂了,但是子进程都还没结束,那么他们都是孤儿进程

PS: 多进程在有些时候很好用,但是也有一些弊端,这里也只是简单解密了其中一种方式。没有涉及到进程通信等问题。其实要考虑的东西是可以有很多的,希望大家在合适的场合合适的使用。切不可滥用。

标签:执行,pid,孤儿,dispatchInfo,开发,进程,pcntl,PHP
来源: https://blog.csdn.net/wplblog/article/details/112557253

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有