用写PHP多进程脚本

PHP能否支持多进程?

在日常开发中,我们用到最多的php多进程场景莫过于就是使用php-fpm了,php-fpm作为php的多进程管理器,当我们使用nginx作为webserver时,来自用户的请求会根据nginx的路由配置把以php为后缀的文件转发给我们的php-fpm,而这里的php-fpm就是一个多进程管理器,多个用户的同时请求api,那么php-fpm则会开启多个php的处理进程进行处理。说了php-fpm,我们使用php的多进程方式还有swoole,pcntl_fork等,而这里我讲的主要是php在写服务器脚本使用多线程的情况下,首先要使用pcntl_fork函数,我们必须先检查php是否支持该扩展,使用php -m 查看php所有扩展。如果有pcntl则说明是安装了该多进程扩展,可以使用多进程功能,如果没有,则需要自行去安装该扩展。

检查是否存在pcntl

二,关于pcntl_fork的介绍

当我们调用pcntl_fork函数时,成功时,在父进程执行线程内返回产生的子进程的PID,在子进程执行线程内返回0。失败时,在 父进程上下文返回-1,不会创建子进程,并且会引发一个PHP错误。是这样的,我们调用函数创建进程的时候,函数执行时有时间的,而新的进程刚好是在函数执行开始和结束之间创建出来的,这样,新的进程也执行了这个函数,所以函数也需要有返回值。那么对于该函数一次执行之后,父进程和子进程都会受到该函数的返回值,由于父进程创建了子进程,而子进程并没有创建新的进程,所以子进程对于这个函数的返回结果是没有的,所以就给他赋了一个0。而父进程创建了子进程,子进程是存在pid的,所以就得到了那个进程的pid。

所以综上我们可以发现调用pcntl_fork可能返回的值有三个,分别是-1,0,N,所以我们在使用pcnt_fork时需要分别对三个返回值做对应的判断,当返回值是-1的时候则说明创建子进程失败,当返回值是0的时候说明正是子进程,这是我们则可以在if分支下处理需要子进程执行的任务,当返回值是一N(这里N>0),则说明这是父进程的分支。

三,避免僵尸进程之pcntl_waitpid的使用

何为僵尸进程,就是存在一个进程已经执行完任务,但是该进程未被系统和父进程回收的情况下,那么该进程就是一个存在系统当中,不会做任何事,且一直存在的进程,那么该进程就是一个僵尸进程,在这里,有一种情况是,父进程创建子进程后,父进程比子进程先做完任务然后终止,那么子进程在做完任务后无法被父进程处理,子进程就会变为僵尸进程,这个时候则需要使用pcntl_waitpid的函数来避免这种情况。这个函数的功能的是,当父进程执行这个函数,那么父进程就会一直处于等待状态下,直到子进程完成任务,父进程回收子进程。所以我们在进行php的多进程脚本编程的时候需要父进程执行该函数,用于避免父进程早早退出,无法回收子进程的情况。

四:实战环节

这周接到一个需求,当商品状态发生变化,需要给关注该商品的用户推送一条短信。

需求分析:

1,当商品发生变化,查询关注该商品的用户id,并且把该uid存入redis集合。

2,使用crontab每天晚上九点半,执行脚本对用户推送短信。

考虑到若该商品关注的用户量大数量级在10万个用户,那么我们用单进程的方式推送短信,在php的同步阻塞模式下,单进程时非常耗费执行时间的。所以我们这里要考虑用多进程的方式,开启10个进程分别对10万个用户进行推送,那么在原有的时间耗费上,我们可以提高10倍的效率。

程序思路:10个进程处理10万条推送,那么单个进程一次需要处理1万条推送。

  • 首先我们需要拿到redis的集合的总量,这里假设总量是10万。
  • 然后我们用总量除以进程数,ceil(总量/进程数量),这里用到了ceil函数,不了解的可以搜索该函数功能
  • 然后,for循环开启十个进程。
  • 然后,单个进程我传入参数上面相除得到的值,在单个进程里面执行这个次数。

发送短信代码片段:

    /**
     * 该function用于处理促销redis集合.
     *
     * @param integer $time 多进程下单次循环次数.
     *
     * @return void
     */
    public function managePromotionRedis($time)
    {
        $today = date("Y-m-d");
        $cacheName = $today.'promotion';
        $count = 0;
        while ($count <= $time) {
            $uid = \Module_Base::Instance()->mRedis()->sPop($cacheName);
            if (empty($uid)) {
                $this->log->writeLine($cacheName.'队列已经空'.PHP_EOL);
                return;
            }
            $pid = getmypid();
            $this->sendMsgToUid($uid,1);
            $this->log->writeLine($cacheName.'队列出了'.$uid.PHP_EOL);
            echo $cacheName.'队列出了'."$pid".'这个uid是'.$uid.PHP_EOL;
            $count++;
        }
        return;
    }

这里我们传入一个参数,$time代表的是一个进程单次推送短信次数

开启十个进程片段:

    /**
     * 该function用于多进程处理消息推送.
     *
     * @return void
     */
    public function process()
    {
        if (function_exists('pcntl_fork')) {
            $pids = array();
            for ($x = 0; $x < $this->processNumber; $x++) {
                $pid = pcntl_fork();
                if ($pid == -1) {
                    $this->log->writeLine("创建子进程失败!!\n");
                } elseif ($pid) {
                    // 父进程.
                    echo '父进程最后执行'.PHP_EOL;
                    $pids[] = $pid;
                } else {
                    // 子进程.
                    $this->run($this->processNumber);
                    echo '测试'.PHP_EOL;
                    exit();
                }
            }
            // 等待子进程结束
            foreach ($pids as $pid) {
                pcntl_waitpid($pid, $status);
            }
        } else {
            echo '当前PHP版本不支持pcntl_fork功能,运行失败'.PHP_EOL;
            exit();
        }
    }

    /**
     * 该function用于同时执行上新和促销redis集合.
     *
     * @param integer $processNumber 进程数量.
     *
     * @return void
     */
    public function run($processNumber)
    {

        try {
            $today = date("Y-m-d");
            $promotionCount = \Module_Base::Instance()->mRedis()->SCARD($today.'promotion');
            $newProjectCount = \Module_Base::Instance()->mRedis()->SCARD($today.'newProduct');
            $SinglePromotionRunTime = ceil($promotionCount / $processNumber);
            $SingleNewProductTime = ceil($newProjectCount / $processNumber);
            $this->manageNewProductRedis($SingleNewProductTime);
            $this->managePromotionRedis($SinglePromotionRunTime);
        } catch (Exception $exception) {
            $this->log->writeLine($exception->getMessage());
            exit();
        }
    }

    

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值