PHP多进程

        我们都知道PHP是单进程执行的,PHP处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,但PHP实现多进程也意义重大,尤其是在后台Cli模式下处理大量数据或运行后台DEMON守护进程时,多进程的优势 : 一个任务被分解成多个进程执行,就会减少整体的耗时


        1.多开几个进程,这种方式简单实用,推荐,比如说使用shell脚本:

#!/bin/bash

for((i=1;i<=8;i++))
do    
    /usr/bin/php multiprocessTest.php &
done

wait


        2.php多进程

        php多进程需要pcntl,可以通过 php -m 查看,而且多进程实现只能在cli模式下。

        注:php多进程一般应用在php_cli命令行中执行php脚本,做进程任务时要检查php是否开启了pcntl扩展,(pcntl是process control进程管理的缩写。PHP的进程控制支持实现了Unix方式的进程创建, 程序执行, 信号处理以及进程的中断(此扩展在 Windows 平台上不可用)。该扩展在PHP中进程控制支持默认是关闭的。


        1)、安装pcntl扩展

        a)、未安装php7时添pcntl扩展

                在配置编译中增加代码 --enable-pcntl :

./configure --enable-pcntl

        b)、已安装php7添加pcntl扩展

###1、进入php源码包里  扩展库目录
[root@localhost ~]# cd /usr/local/src/php-7.1.22
[root@localhost php-7.1.22]# cd ext/pcntl/

###2、用phpize生成配置文件
[root@localhost pcntl]# /usr/local/php/bin/phpize

###3、指定配置文件进行配置
[root@localhost pcntl]#./configure --enable-pcntl --with-php-config=/usr/local/php/bin/php-config

###4、编译安装
[root@localhost pcntl]# make && make install
.....
Installing shared extensions:     /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/
[root@localhost pcntl]#

###5、切换到 extensions 目录:
[root@localhost ~]# cd /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/
[root@localhost no-debug-non-zts-20170718]# ls
opcache.a  opcache.so  pcntl.so

###6、查看 php.ini 实际位置,编辑 php.ini文件
[root@localhost etc]# vim php.ini
添加:
extension=/usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/pcntl.so

###7、添加后 重新启动 php-fpm.service 生效
[root@localhost ~]# systemctl restart  php-fpm.service

###8、查看 phpinfo() 信息。

        2)、创建子进程

        创建PHP子进程是多进程的开始,我们需要 pcntl_fork() 函数;


        pcntl_fork() — 在当前进程当前位置产生分支(子进程)。此函数创建了一个新的子进程后,子进程会继承父进程当前的上下文,和父进程一样从pcntl_fork()函数处继续向下执行,只是获取到的pcntl_fork()的返回值不同,我们便能从判断返回值来区分父进程和子进程,分配父进程和子进程去做不同的逻辑处理。


        pcntl_fork()函数成功执行时会在父进程返回子进程的进程id(pid)。fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程 号,而子进程得到的是0。

        而pcntl_fork()函数在执行失败时,会在父进程返回-1,当然也不会有子进程产生。

<?php

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

          

getmypid();         // 返回当前 PHP 进程 ID,或在错误时返回 FALSE。

pcntl_fork();       // 成功时,在父进程执行线程内返回产生的子进程的PID,在子进程执行线程内返回0。
                    // 失败时,在 父进程上下文返回-1,不会创建子进程,并且会引发一个PHP错误。
posix_getpid();     // 获取当前进程的pid;

        以下是fork子进程的一个简单的小例子:

<?php
foreach (range(1, 5) as $index) {
    $pid = pcntl_fork();    
    if ($pid === -1) {        
        echo "failed to fork!\n";        
        exit;
    } elseif ($pid) {
        pcntl_wait($status); //父进程必须等待一个子进程退出后,再创建下一个子进程。
        echo "I am the parent, pid: $pid\n";
    } else {
        $cid = posix_getpid();        
        echo "fork the {$index}th child, pid: $cid\n";        
        exit; //必须
    }
}

        这个例子非常简单,循环创建5个进程,在各个进程里面打印一句话,主要使用的方法就是函数 pcntl_fork,一次调用两次返回,在父进程中返回子进程pid,在子进程中返回0,出错返回-1。

        执行结果如下:

fork the 1th child, pid: 7326
I am the parent, pid: 7326
fork the 2th child, pid: 7327
I am the parent, pid: 7327
fork the 3th child, pid: 7328
I am the parent, pid: 7328
fork the 4th child, pid: 7329
I am the parent, pid: 7329
fork the 5th child, pid: 7330
I am the parent, pid: 7330

        先解释一下为什么会产生10条打印结果,第一条结果是子进程打印的,第二条是在父进程打印的!

        1、如果是在循环中创建子进程,那么子进程中最后要exit,防止子进程进入循环!

        2、必须等待子进程执行完任务, 有一个简单方法是使用 pcntl_wait,如果不加这个你会发现一个是执行的顺序不固定,第二个就是创建的进程会少于5个,但是加了你会发现这个完全变成并行了...上面的结果就是

        然后找了找,发现下面这种写法:

<?php
    $ids = [];
    
    foreach (range(1, 5) as $index) {
        $ids[] = $pid = pcntl_fork();    
        if ($pid === -1) {        
            echo "failed to fork!\n";        
            exit;
        } 
        elseif ($pid) 
        {        
            echo "I am the parent, pid: $pid\n";
        } 
        else 
        {
            $cid = posix_getpid();        
            echo "fork the {$index}th child, pid: $cid\n";        
            exit;
        }
    }
    
    foreach ($ids as $i => $pid) {    
        if ($pid) {
            pcntl_waitpid($pid, $status);
        }
    }
    
    
结果如下:
I am the parent, pid: 19054
fork the 1th child, pid: 19054
I am the parent, pid: 19055
fork the 2th child, pid: 19055
I am the parent, pid: 19056
fork the 3th child, pid: 19056
I am the parent, pid: 19057
I am the parent, pid: 19058
fork the 5th child, pid: 19058
fork the 4th child, pid: 19057

        找了一张图,大体解释了总体流程:

        冷暖自知一抹茶ck

        几行代码就可以写出一个多进程程序,实现并行编程。


        管理子进程:

        创建好了进程,那么怎么对子进程进行管理呢?使用信号。

        在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。

        

        分发信号处理器:

        我们通过在父进程接收子进程传来的信号,判断子进程状态,来对子进程进行管理。

        我们需要在父进程里使用pcntl_signal()函数和pcntl_signal_dispatch()函数来给各个子进程安装信号处理器。

pcntl_signal (int $signo , callback $handler) 安装一个信号处理器;
        $signo是待处理的信号常量,callback是其处理函数

pcntl_signal_dispatch () 调用每个等待信号通过pcntl_signal()安装的处理器

        PHP内常见的信号常量有:

        SIGCHLD     子进程退出成为僵尸进程会向父进程发送此信号
        SIGHUP      进程挂起
        SIGTEM      进程终止
        ...         // 其他请在手册中查看

        安装并调用信号处理器后,一旦子进程有相应的信号返回给父进程,父进程就可以调用相应的callback函数对子进程处理;

<?php

    //ctrl+cp
    cntl_signal(SIGINT, function () {
        fwrite(STDOUT, "receive signal: " . SIGINT . " do nothing ...\n");
    });
    
    //kill
    pcntl_signal(SIGTERM, function () {
        fwrite(STDOUT, "receive signal: " . SIGTERM . " I will exit!\n");    
        exit;
    });
    
    while (true) {
    pcntl_signal_dispatch();    
    echo "do something。。。\n";
    sleep(5);
}

        Linux进程信号分为很多种,kill -l 可以查看,PHP里面定义了43种,咱就说说常用的几种:

        SIGINT 2 这个其实相对于 ctrl+c

        SIGTERM 15 就是 kill 默认的参数,表示终止信号,但是你发了信号程序不一定响应

        SIGKILL 9 就是 kill -9, 表示立马终止,这个信号在PHP里面是无法注册的,所以一定能成功


        看明白了这个就可以读懂上面的例子了,其中 pcntl_signal 是注册信号处理handler,第一个参数是你需要注册的信号,第二个是处理操作,可以是匿名函数或者一个函数名,可以注册多个信号。pcntl_signal_dispatch 调用每个等待信号通过pcntl_signal() 安装的处理器。早期PHP还有一种写法是使用 ticks,性能非常差,php5.3之后建议都使用 pcntl_signal_dispatch。


        说明一下:pcntl_signal()函数仅仅是注册信号和它的处理方法,真正接收到信号并调用其处理方法的是pcntl_signal_dispatch()函数必须在循环里调用,为了检测是否有新的信号等待dispatching。


        上面的例子执行结果就是当你使用 ctrl+c 的话是无法终止程序的,只有使用 kill pid 这种形式才可以,但是并不是立马就退出,它是代码执行到循环顶部 pcntl_signal_dispatch 地方的时候才会退出,这就保证了你使用kill杀掉进程的时候并不会丢失数据,说好听点这也算是平滑重启吧!



        处理子进程:

        对子进程的处理方法有:

        posix_kill():此函数并不能顾名思义,它通过向子进程发送一个信号来操作子进程,在需要要时可以选择给子进程发送进程终止信号来终止子进程;

        pcntl_waitpid():等待或返回fork的子进程状态,如果指定的子进程在此函数调用时已经退出(俗称僵尸进程),此函数将立刻返回,并释放子进程的所有系统资源,此进程可以避免子进程变成僵尸进程,造成系统资源浪费;

        下面是两个函数的函数原型:

// 向进程id为$pid的进程发送$sig信号,$sig常见信号如上;
bool posix_kill ( int $pid , int $sig ) 

// 挂起当前进程的执行直到进程号为$pid的进程退出(如果$pid为-1,则等待任意一个子进程); 
int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )


        PHP多进程优点:

        1.使用多进程, 子进程结束以后, 内核会负责回收资源;

        2.使用多进程,子进程异常退出不会导致整个进程Thread退出. 父进程还有机会重建流程.

        3.一个常驻主进程, 只负责任务分发, 逻辑更清楚.



相关链接:

        PHP 如何创建守护(daemon)进程 : http://php-note.com/article/detail/7022ca840e924a9d8914d6d582edef33 

        

冷暖自知一抹茶ck
请先登录后发表评论
  • 最新评论
  • 总共0条评论