专栏名称: 狗厂
目录
相关文章推荐
51好读  ›  专栏  ›  狗厂

PHP多进程系列笔记(一)

狗厂  · 掘金  ·  · 2018-06-14 10:09

正文

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

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

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

pcntl_fork

  1. int pcntl_fork ( void )

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

fork.php

  1. <?php

  2. $pid = pcntl_fork();

  3. if($pid == -1){

  4.    //错误处理:创建子进程失败时返回-1.

  5.    die( 'could not fork' );

  6. }elseif($pid){

  7.    //父进程会得到子进程号,所以这里是父进程执行的逻辑

  8.    $id = getmypid();  

  9.    echo "Parent process,pid {$id}, child pid {$pid}\n";  

  10. }else{

  11.    //子进程得到的$pid为0, 所以这里是子进程执行的逻辑

  12.    $id = getmypid();  

  13.    echo "Child process,pid {$id}\n";  

  14.    sleep(10);

  15. }

命令行运行:

  1. $ php fork.php

  2. Parent process,pid 98, child pid 99

  3. Child process,pid 99

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

                        
  1. [ root@9355490fe5da /]# ps -ef | grep php

  2. root       105     1  0 16:46 pts/0    00: 00:00 php fork.php

  3. 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 了。

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

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

我们修改代码:

  1. <?php

  2. $pid = pcntl_fork();

  3. if($pid == -1){

  4.    exit("fork fail");

  5. }elseif($pid){

  6.    $id = getmypid();  

  7.    echo "Parent process,pid {$id}, child pid {$pid}\n";  

  8.    pcntl_wait($status);

  9.    //pcntl_waitpid($pid, $status);

  10. }else{

  11.    $id = getmypid();  

  12.    echo "Child process,pid {$id}\n";  

  13.    sleep(10);

  14. }

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

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

  1. 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。

示例:

  1. <?php

  2. $pid = pcntl_fork();

  3. if($pid == -1){

  4.    exit("fork fail");

  5. }elseif($pid){

  6.    $id = getmypid();  

  7.    echo "Parent process,pid {$id}, child pid {$pid}\n";  

  8.    while(1){

  9.        $res = pcntl_wait($status, WNOHANG);

  10.        //$res = pcntl_waitpid($pid, $status, WNOHANG);

  11.        if ($res == -1 || $res > 0){

  12.            sleep(10);//此处为了方便看效果,实际不需要

  13.            break;

  14.        }

  15.    }

  16. }else{

  17.    $id = getmypid();  

  18.    echo "Child process,pid {$id}\n";  

  19.    sleep(2);

  20. }

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

  1. <?php

  2. $child_pids = [];

  3. for($i=0;$i<3; $i++){

  4.    $pid = pcntl_fork();

  5.    if($pid == -1){

  6.        exit("fork fail");

  7.    }elseif($pid){

  8.        $child_pids[] = $pid;

  9.        $id = getmypid();  

  10.        echo time()." Parent process,pid {$id}, child pid {$pid}\n";  

  11.    }else{

  12.        $id = getmypid();

  13.        $rand =   rand(1,3);

  14.        echo time()." Child process,pid {$id},sleep $rand\n";  

  15.        sleep($rand); //#1 故意设置时间不一样

  16.        exit();//#2 子进程需要exit,防止子进程也进入for循环

  17.    }

  18. }

  19. while(count($child_pids)){

  20.    foreach ($child_pids as $key => $pid) {

  21.        // $res = pcntl_wait($status, WNOHANG);

  22.        $res = pcntl_waitpid($pid, $status, WNOHANG);//#3







请到「今天看啥」查看全文