`

基于PCNTL的PHP并发编程

    博客分类:
  • PHP
 
阅读更多

PHP是一门较早出现的WEB开发脚本语言,并由于其语法结构简单、易学、开源等特性迅速占领WEB开发脚本语言领域,并成为这个领域的龙头老大直至今日。PHP从一出生就被设计用来快速开发WEB应用,这也注定了它在某些方面的先天不足,例如在cli环境下处理大量数据的情况,或者在并发编程方面,都显得力不从心。

 

本文主要讲解基于PCNTL的PHP并发编程,虽然PHP本身不支持多进程,但基于LINUX的PHP扩展PCNTL却可以提供多进程编程。网络上很多同类文章,但笔者进行多次尝试后发现,不是难以控制进程数量,就是有潜在产生僵尸进程或孤儿进程的危险,或者父进程阻塞难以获得更大的并发效果,且大多没有介绍FORK的原理,使得PHP程序员学习PCNTL并发编程尤为困难。本文力求解决这个问题。

 

FORK编程的大概原理是,每次调用fork函数,操作系统就会产生一个子进程,儿子进程所有的堆栈信息都是原封不动复制父进程的,而在fork之后,父进程与子进程实际上是相互独立的,父子进程不会相互影响。也就是说,fork调用位置之前的所有变量,父进程和子进程是一样的,但fork之后则取决于各自的动作,且数据也是独立的;因为数据已经完整的复制给了子进程。而唯一能够区分父子进程的方法就是判断fork的返回值。如果为0,表示是子进程,如果为正数,表示为父进程,且该正数为子进程的PID(进程号),而如果是-1,表示子进程创建失败。请看如下代码:

 

<?php
$pid = pcntl_fork();
 
switch($pid){
case -1:
    echo "couldn't fork";
    break;
case 0:
    echo "I'm parent";
    break;
default :
    echo "I'm child";
}
?>

 

以上代码会产生一个子进程,并根据返回的pid进行父子进程的判断。

 

生产环境中以上代码大多是不适用的,我们需要大量的并发进程为同时为我们处理事情。这时,我们就需要fork多次,而产生的子进程数量需要在我们的控制之中,否则无限制的fork只会拖垮服务器。笔者曾经有过经历,几秒钟服务器负载从0.3左右飙到800多,吓的一身冷汗。

 

而子进程的使用通常会涉及到两种:子进程执行完任务直接退出;子进程常驻内存,等待任务。以上两种方式适用于不同情况。第一种情况大多我们不需要考虑太多,除非子进程的创建是循环进行的。而第二种则需要考虑进程间通信。

 

无论哪一种,无可避免的一个问题就是僵尸进程。僵尸进程就是子进程退出后,父进程没有及时回收,系统仍然保留子进程的执行信息(例如PID,退出状态等),留待其他程序读取。如果僵尸进程数量很少,我们可以忽略掉。但如果是在一个循环中fork(并发编程中常见的死循环),这个问题就不能无视了,父进程必须定期回收已经退出的子进程。子进程的回收我们采用pcnt_wait函数来完成。如下面这段代码:

 

<?php
while(true){
    $pid = pcntl_fork();
    switch($pid){
    case -1:
        echo "couldn't fork";
        break;
    case 0:{
        $subPid = pcntl_wait($status);
        var_dump($status);
        break;
    }
    default :
        echo "I'm child";
        exit(0);
    }
}

 

以上代码能够循环产生子进程,并且父进程会阻塞等待子进程退出,这样就产生了一个问题,父进程必须等待一个子进程退出后,再创建另外一个。额,这还是串行执行的不是吗?是的,解决办法就是将pcntl_wait函数替换成pcntl_waitpid()并添加WNOHANG参数。该函数可以在没有子进程退出的情况下立刻跳出执行后续代码。能够达到更好的并发效果。具体代码这里不再演示。

 

循环创建子进程是一件非常浪费操作系统资源的事情。既然使用了死循环来处理任务,那么就说明任务是一个可以队列化的数据结构。我们可以采用进程间的通信,解决子进程退出重建的问题。而通信的机制主要有信号量、管道、共享内存等。然后我们需要一个生产者和消费者的模型。而基于fork的这种代码编写方式,非常不利于我们编写复杂的业务逻辑。所以建议将进程控制与业务处理的代码进程抽象隔离。进程间通信本文暂不涉及,如果读者有需要可以阅读关于管道、共享内存和信号量的文章。

 

根据上面所说,循环创建子进程会造成系统资源的浪费,而循环创建往往意味着任务可以队列化。我们可以创建子进程后,让子进程常驻内存,持续执行等待任务到达。而这类模型往往可以用生产-消费模型来实现。生产者负责将任务写入队列,而子进程从队列中取出任务并执行。队列的实现最好采用本身支持互斥的方式,这样可以降低代码的复杂度,管道是个不错的选择。

 

基于fork方式实现的多进程,由于我们只能使用Pid来做代码隔离,所以进程控制中会充斥的各种if、else或者switch。这对实生产者和消费者模型造成一定难度。以下是一个生产者消费者的模型:

 

<?php
/**
 * @author:Jenner
 * @date 2014-01-14
 */
class JetMultiProcess {
 
    //最大队列长度
    private $size;
 
    private $curSize;
 
    //生产者
    private $producer;
 
    //消费者
    private $worker;
 
    /**
     * 构造函数
     * @param string $worker 需要创建的消费者类名
     * @param int $size 最大子进程数量
     * @param $producer 需要创建的消费者类名
     */
    public function __construct($producer, $worker, $size=10){
        $this->producer = new $producer;
        $this->worker = $worker;
        $this->size = $size;
        $this->curSize = 0;
    }
 
    public function start(){
 
        $producerPid = pcntl_fork();
        if ($producerPid == -1) {
            die("could not fork");
        } else if ($producerPid) {// parent
 
            while(true){
                $pid = pcntl_fork();
                if ($pid == -1) {
                    die("could not fork");
                } else if ($pid) {// parent
 
                    $this->curSize++;
                    if($this->curSize>=$this->size){
                        $sunPid = pcntl_wait($status);
                                                $this->curSize--;
                    }
 
                } else {// worker
 
                    $worker = new $this->worker;
                    $worker->run();
                    exit();
                }
            }
 
        } else {// producer
            $this->producer->run();
            exit();
        }
    }
}

 

以上代码,通过size控制多进程数量,通过构造函数传入生产者和消费者的类型。父进程第一次fork产生一个子进程生产者,然后再进行size次fork创建多个消费者。类似方法可以创建多个生产者和多个消费者协同工作。生产者和消费者都必须实现run方法,并在run方法中创建死循环。循环写入和读取队列进行协同工作。该类没有提供进程间通信的功能。通信需要在生产者和消费者类中实现。这样能够使得进程控制的代码看起来更加简洁。

 

该模型已经应用于大型项目,每天承载数万次的任务写入。

分享到:
评论

相关推荐

    基于PCNTL的PHP并发处理封装类

    基于PCNTL的PHP并发处理 封装类

    php多进程并发编程防止出现僵尸进程的方法分析

    本文实例讲述了php多进程并发编程防止出现僵尸进程的方法。分享给大家供大家参考,具体如下: 对于用PHP进行多进程并发编程,不可避免要遇到僵尸进程的问题。 僵尸进程是指的父进程已经退出,而该进程dead之后没有...

    利用PCNTL实现PHP代码异步和并行运行

    这个库为PHP的PCNTL扩展提供了一个小而简单的封装。 它允许使用易于使用的API并行运行差异流程。

    PHP使用pcntl_fork实现多进程下载图片的方法

    主要介绍了PHP使用pcntl_fork实现多进程下载图片的方法,较为详细的分析了pcntl_fork的原理与用法,以及使用pcntl_fork实现多进程下载图片的方法,非常具有实用价值,需要的朋友可以参考下

    PHP多进程之pcntl_fork的实例详解

    PHP多进程编之pcntl_fork的实例详解 其实PHP是支持并发的,只是平时很少使用而已。平时使用最多的应该是使用PHP-FMP调度php进程了吧。 但是,PHP的使用并不局限于做Web,我们完全也可以使用PHP来进行系统工具类的...

    分享PHP-pcntl 实现多进程代码

    PHP可通过PCNTL扩展实现进程控制,如进程创建,信号处理,进程中断判断等。但只能在CLI模式下操作。PCNTL的信号机制是基于 ticks 机制实现的。

    Linux系统中为php添加pcntl扩展

    pcntl扩展可以支持php的多线程操作(仅限linux) 原本需要重新编译PHP的后面configrue提示加上–enable-pcntl 由于我的php是采用yum安装的,所以不能采用上面的方式 下面介绍一个php动态添加扩展的方式 phpize 1、...

    mac系统下为 php 添加 pcntl 扩展

    pcntl中php实现多进程必须要安装的扩展,本文给大家简单介绍下如何在mac系统中为 php 添加 pcntl 扩展

    PHP的pcntl多进程用法实例

    主要介绍了PHP的pcntl多进程用法,实例分析了pcntl操作多进程的使用技巧,非常具有实用价值,需要的朋友可以参考下

    php pcntl_fork和pcntl_fork 的用法

    pcntl_fork()函数就是为当前的进程创建一个子进程。并且先运行父进程,返回的是子进程的PID,肯定大于零。在父进程的代码中可以用pcntl_fork(&$status)暂停父进程知道他的子进程有返回值。注意:父进程的阻塞...

    php中pcntl_fork创建子进程的方法实例

    一、php中pcntl_fork函数概述 pcntl_fork()函数是php中用于创建子进程的一个函数,返回创建的子进程的pid。 该函数创建子进程具体fork的过程: (1)调用该函数即创建一个子进程,创建成功父进程返回子进程的pid,...

    最新的php7.2.10 windows版本官方下载地址

    最新的php7.2.10 windows版本官方下载地址,支持windows7, windows 10,正版

    php-5.6.37.tar.gz下载及php5.6源码安装说明

    --enable-pcntl\ --enable-mbstring\ --enable-soap\ --enable-zip\ --enable-calendar\ --enable-bcmath\ --enable-exif\ --enable-ftp\ --enable-intl\ --with-openssl\ --with-zlib\ --with-curl\ -...

    php-multi-process-runner:基于PHP拓展PCNTL的多进程执行工具

    基于 PHP 拓展 的多进程执行工具。 Usage 下载 pmr 到本地 wget https://raw.githubusercontent.com/overtrue/php-multi-process-runner/master/pmr 添加权限 sudo chmod +x ./pmr 语法: ./pmr 命令 最大进程数 多...

Global site tag (gtag.js) - Google Analytics