zoukankan      html  css  js  c++  java
  • PHP多进程系列笔记(一)

    本系列文章将向大家讲解pcntl_*系列函数,从而更深入的理解进程相关知识。

    PCNTL在PHP中进程控制支持默认是关闭的。您需要使用 --enable-pcntl 配置选项重新编译PHP的 CGI或CLI版本以打开进程控制支持。

    如果自带的PHP没有安装pcntl扩展,可以下载相同版本的源码,进入ext/pcntl使用phpize编译安装。

    Note: 此扩展在 Windows 平台上不可用。

    pcntl_fork

    int pcntl_fork ( void )
    

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

    fork.php

    <?php 
    
    $pid = pcntl_fork();
    
    if($pid == -1){
        //错误处理:创建子进程失败时返回-1.
        die( 'could not fork' );
    }elseif($pid){
        //父进程会得到子进程号,所以这里是父进程执行的逻辑
        $id = getmypid();   
        echo "Parent process,pid {$id}, child pid {$pid}
    ";   
    }else{
        //子进程得到的$pid为0, 所以这里是子进程执行的逻辑
        $id = getmypid();   
        echo "Child process,pid {$id}
    ";   
        sleep(10); 
    }
    

    命令行运行:

    $ php fork.php
    Parent process,pid 98, child pid 99
    Child process,pid 99
    

    该例里父进程还没有来得及等子进程运行完毕就自动退出了,子进程由 init进程接管。通过 ps -ef | grep php 看到子进程还在运行:

    [root@9355490fe5da /]# ps -ef | grep php
    root       105     1  0 16:46 pts/0    00:00:00 php fork.php
    root       107    27  0 16:46 pts/1    00:00:00 grep php
    

    子进程成为孤立进程,ppid(父进程id)变成1了。如果在父进程里也加个sleep(5),你会看到子进程ppid本来是大于1的,后来就变成1了。

    注:如果是docker环境,孤立进程的ppid可能是0。

    pcntl_wait

    pcntl_wait()函数用来让父进程等待子进程退出,默认情况下会阻塞主进程。

    阻塞模式

    紧接着上面的例子,如果想等子进程运行结束后父进程再退出,该怎么办?那就用到pcntl_wait了。

    int pcntl_wait ( int &$status [, int $options = 0 ] )
    

    该函数阻塞当前进程,只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。

    我们修改代码:

    <?php 
    
    $pid = pcntl_fork();
    if($pid == -1){
        exit("fork fail");
    }elseif($pid){
        $id = getmypid();   
        echo "Parent process,pid {$id}, child pid {$pid}
    ";   
        pcntl_wait($status);
        //pcntl_waitpid($pid, $status);
    }else{
        $id = getmypid();   
        echo "Child process,pid {$id}
    ";   
        sleep(10); 
    }
    

    此时再次运行程序,父进程就会一直等待子进程运行结束然后退出。

    pcntl_waitpid()pcntl_wait()功能相同。前者第一个参数支持指定pid参数,当指定-1作为pid的值等同于后者。

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

    当已知子进程pid的时候,可以使用pcntl_waitpid()

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

    返回值为退出的子进程进程号时,想了解如何退出,可以通过 $status状态码反应。

    非阻塞模式

    pcntl_wait()默认情况下会阻塞主进程,直到子进程执行完毕才继续往下运行。如果设置最后一个参数为常量WNOHANG,那么就不会阻塞主进程,而是继续执行后续代码, 此时 pcntl_waitpid 就会返回0。

    示例:

    <?php 
    
    $pid = pcntl_fork();
    if($pid == -1){
        exit("fork fail");
    }elseif($pid){
        $id = getmypid();   
        echo "Parent process,pid {$id}, child pid {$pid}
    ";   
    
        while(1){
            $res = pcntl_wait($status, WNOHANG);
            //$res = pcntl_waitpid($pid, $status, WNOHANG);
            if ($res == -1 || $res > 0){
                sleep(10);//此处为了方便看效果,实际不需要
                break;
            }
        } 
    }else{
        $id = getmypid();   
        echo "Child process,pid {$id}
    ";   
        sleep(2); 
    }
    

    该示例里只有一个子进程,看不出来非阻塞的好处,我们修改一下:

    <?php 
    
    $child_pids = [];
    
    for($i=0;$i<3; $i++){
        $pid = pcntl_fork();
        if($pid == -1){
            exit("fork fail");
        }elseif($pid){
            $child_pids[] = $pid;
    
            $id = getmypid();   
            echo time()." Parent process,pid {$id}, child pid {$pid}
    ";   
        }else{
            $id = getmypid(); 
            $rand =   rand(1,3);
            echo time()." Child process,pid {$id},sleep $rand
    ";   
            sleep($rand); //#1 故意设置时间不一样
            exit();//#2 子进程需要exit,防止子进程也进入for循环
        }
    }
    
    while(count($child_pids)){
        foreach ($child_pids as $key => $pid) {
            // $res = pcntl_wait($status, WNOHANG);
            $res = pcntl_waitpid($pid, $status, WNOHANG);//#3
            if ($res == -1 || $res > 0){
                echo time()." Child process exit,pid {$pid}
    ";   
                unset($child_pids[$key]);
            }else{
                // echo time()." Wait End,pid {$pid}
    ";   //#4
            }
        }
        
    } 
    

    #3处首先先去掉WNOHANG参数,运行:

    $ php fork.1.php 
    1528637334 Parent process,pid 6600, child pid 6601
    1528637334 Child process,pid 6601,sleep 2
    1528637334 Parent process,pid 6600, child pid 6602
    1528637334 Child process,pid 6602,sleep 2
    1528637334 Parent process,pid 6600, child pid 6603
    1528637334 Child process,pid 6603,sleep 1
    1528637336 Child process exit,pid 6601
    1528637336 Child process exit,pid 6602
    1528637336 Child process exit,pid 6603
    

    我们看到,6603号进程运行时间最短,但是是最后回收。我们再加上WNOHANG参数,运行:

    $ php fork.1.php 
    1528637511 Parent process,pid 6695, child pid 6696
    1528637511 Child process,pid 6696,sleep 2
    1528637511 Parent process,pid 6695, child pid 6697
    1528637511 Child process,pid 6697,sleep 1
    1528637511 Parent process,pid 6695, child pid 6698
    1528637511 Child process,pid 6698,sleep 3
    1528637512 Child process exit,pid 6697
    1528637513 Child process exit,pid 6696
    1528637514 Child process exit,pid 6698
    

    6697进程最先回收!说明确实是异步非阻塞的。感兴趣的朋友还可以开启#4处代码,未使用WNOHANG参数的时候,里面的代码是不会运行的。

    注意:#2处需要注意子进程需要exit,防止子进程也进入for循环。如果没有exit(),最终创建的子进程不只3个。

    检测status函数

    pcntl_waitpcntl_waitpid两个函数中的$status中存了子进程的状态信息,这个参数可以用于 pcntl_wifexitedpcntl_wifstoppedpcntl_wifsignaledpcntl_wexitstatuspcntl_wtermsigpcntl_wstopsigpcntl_waitpid这些函数。

    代码片段:

    while(1){
    $res = pcntl_wait($status);
    if ($res == -1 || $res > 0){
    
        if(!pcntl_wifexited($status)){
            //进程非正常退出
            echo "service exit unusally; pid is $pid
    ";
        }else{
            //获取进程终端的退出状态码;
            $code = pcntl_wexitstatus($status);
            echo "service exit code: $code;pid is $pid 
    ";
        }
    
        if(!pcntl_wifsignaled($status)){
            //不是通过接受信号中断
            echo "service term not by signal;pid is $pid 
    ";
        }else{
            $signal = pcntl_wtermsig($status);
            echo "service term by signal $signal;pid is $pid
    ";
        }
    
        if(!pcntl_wifstopped($status)){
            echo "service stop not unusally;pid is $pid 
    ";
        }else{
            $signal = pcntl_wstopsig($status);
            echo "service stop by signal $signal;pid is $pid
    ";
        }
    
        break;
    }
    
    1. pcntl_wifexited — 检查状态代码是否代表一个正常的退出。当子进程状态代码代表正常退出时返回 TRUE ,其他情况返回 FALSE 。pcntl_wexitstatus()
      返回一个中断的子进程的返回代码。这个函数仅在函数 pcntl_wifexited() 返回 TRUE 时有效。
    2. pcntl_wifsignaled — 检查子进程状态码是否代表由于某个信号而中断。如果子进程是由于某个未捕获的信号退出的返回 TRUE ,其他情况返回 FALSE 。cntl_wtermsig返回导致子进程中断的信号编号。这个函数仅在 pcntl_wifsignaled() 返回 TRUE 时有效。
    3. pcntl_wifstopped — 检查子进程当前是否已经停止。如果子进程当前是停止的返回 TRUE ,其他情况返回 FALSE 。pcntl_wstopsig ()
      返回导致子进程停止的信号编号。这个函数仅在 pcntl_wifstopped() 返回 TRUE 时有效。

    参考

    1、php多进程 防止出现僵尸进程
    https://www.cnblogs.com/jkko123/p/6351615.html?utm_source=itdadao&utm_medium=referral
    2、PCNTL函数族--PHP多进程编程 (转)
    https://www.cnblogs.com/zox2011/archive/2013/02/19/2917448.html

  • 相关阅读:
    Oracle 导入导出报错的简单处理
    Windows 下面 winrar 压缩简单记录
    zip 与 unzip的简单使用
    [知乎]山东:一枚神奇独一的“三棱锥”
    【喷嚏图卦】 委内瑞拉崩溃的背后:渐行渐近的石油危机
    [互联网]2018年互联网套路简史
    [wiki]陶德曼调停
    [阮一峰]找回密码的功能设计
    sap 最新财报以及云业务转型情况
    debian 7 安装 rz sz lrzsz
  • 原文地址:https://www.cnblogs.com/52fhy/p/9175704.html
Copyright © 2011-2022 走看看