PHP多进程实践

1. 直接方式

pcntl_fork() 创建一个进程,在父进程返回值是子进程的pid,在子进程返回值是0,-1表示创建进程失败。跟C非常相似。

测试脚本 test.php
<?php
    // example of multiple processes
    date_default_timezone_set( 'Asia/Chongqing');
    echo "parent start, pid ", getmypid(), "\n" ;
    beep();
    for ($i=0; $i<3; ++$i){
          $pid = pcntl_fork();
           if ($pid == -1){
                 die ("cannot fork" );
          } else if ($pid > 0){
                 echo "parent continue \n";
                 for ($k=0; $k<2; ++$k){
                      beep();
                }
          } else if ($pid == 0){
                 echo "child start, pid ", getmypid(), "\n" ;
                 for ($j=0; $j<5; ++$j){
                      beep();
                }
                 exit ;
          }
    }
    // ***
    function beep(){
           echo getmypid(), "\t" , date( 'Y-m-d H:i:s', time()), "\n" ;
          sleep(1);
    }
?>
用命令行运行
#php -f test.php

输出结果
parent start, pid 1793
1793    2013-01-14 15:04:17
parent continue
1793    2013-01-14 15:04:18
child start, pid 1794
1794    2013-01-14 15:04:18
1794    2013-01-14 15:04:19
1793    2013-01-14 15:04:19
1794    2013-01-14 15:04:20
parent continue
1793    2013-01-14 15:04:20
child start, pid 1795
1795    2013-01-14 15:04:20
17931794                2013-01-14 15:04:212013-01-14 15:04:21

1795    2013-01-14 15:04:21
1794    2013-01-14 15:04:22
1795    2013-01-14 15:04:22
parent continue
1793    2013-01-14 15:04:22
child start, pid 1796
1796    2013-01-14 15:04:22
1793    2013-01-14 15:04:23
1796    2013-01-14 15:04:23
1795    2013-01-14 15:04:23
1795    2013-01-14 15:04:24
1796    2013-01-14 15:04:24
1796    2013-01-14 15:04:25
1796    2013-01-14 15:04:26

从中看到,创建了3个子进程,和父进程一起并行运行。其中有一行格式跟其他有些不同,
17931794                2013-01-14 15:04:212013-01-14 15:04:21
因为两个进程同时进行写操作,造成了冲突。


2. 阻塞方式

用直接方式,父进程创建了子进程后,并没有等待子进程结束,而是继续运行。似乎这里看不到有什么问题。如果php脚本并不是运行完后自动结束,而是常驻内存的,就会造成子进程无法回收的问题。也就是僵尸进程。可以通过pcntl_wai()方法等待进程结束,然后回收已经结束的进程。
将测试脚本改成:
$pid = pcntl_fork();
if ($pid == -1){
    ...
} else if ($pid > 0){
     echo "parent continue \n";
     pcntl_wait($status);
     for ($k=0; $k<2; ++$k){
          beep();
    }
} else if ($pid == 0){
     ...
}
用命令行运行
#php -f test.php

输出结果
parent start, pid 1807
1807    2013-01-14 15:20:05
parent continue
child start, pid 1808
1808    2013-01-14 15:20:06
1808    2013-01-14 15:20:07
1808    2013-01-14 15:20:08
1808    2013-01-14 15:20:09
1808    2013-01-14 15:20:10
1807    2013-01-14 15:20:11
1807    2013-01-14 15:20:12
parent continue
child start, pid 1809
1809    2013-01-14 15:20:13
1809    2013-01-14 15:20:14
1809    2013-01-14 15:20:15
1809    2013-01-14 15:20:16
1809    2013-01-14 15:20:17
1807    2013-01-14 15:20:18
1807    2013-01-14 15:20:19
child start, pid 1810
1810    2013-01-14 15:20:20
parent continue
1810    2013-01-14 15:20:21
1810    2013-01-14 15:20:22
1810    2013-01-14 15:20:23
1810    2013-01-14 15:20:24
1807    2013-01-14 15:20:25
1807    2013-01-14 15:20:26

父进程在pcntl_wait()将自己阻塞,等待子进程运行完了才接着运行。


3. 非阻塞方式

阻塞方式失去了多进程的并行性。还有一种方法,既可以回收已经结束的子进程,又可以并行。这就是非阻塞的方式。
修改脚本:
<?php
    // example of multiple processes
    date_default_timezone_set( 'Asia/Chongqing');
    declare (ticks = 1);
    pcntl_signal(SIGCHLD, "garbage" );
    echo "parent start, pid ", getmypid(), "\n" ;
    beep();
    for ($i=0; $i<3; ++$i){
          $pid = pcntl_fork();
           if ($pid == -1){
                 die ("cannot fork" );
          } else if ($pid > 0){
                 echo "parent continue \n";
                 for ($k=0; $k<2; ++$k){
                      beep();
                }
          } else if ($pid == 0){
                 echo "child start, pid ", getmypid(), "\n" ;
                 for ($j=0; $j<5; ++$j){
                      beep();
                }
                 exit (0);
          }
    }
    // parent
    while (1){
           // do something else
          sleep(5);
    }
    // ***
    function garbage($signal){
           echo "signel $signal received\n" ;
          
           while (($pid = pcntl_waitpid(-1, $status, WNOHANG))> 0){
                 echo "\t child end pid $pid , status $status\n" ;
          }
    }
    function beep(){
           echo getmypid(), "\t" , date( 'Y-m-d H:i:s', time()), "\n" ;
          sleep(1);
    }
?>
用命令行运行
#php -f test.php &

输出结果
parent start, pid 2066
2066    2013-01-14 16:45:34
parent continue
2066    2013-01-14 16:45:35
child start, pid 2067
2067    2013-01-14 16:45:35
20662067                2013-01-14 16:45:362013-01-14 16:45:36

2067    2013-01-14 16:45:37
parent continue
2066    2013-01-14 16:45:37
child start, pid 2068
2068    2013-01-14 16:45:37
2067    2013-01-14 16:45:38
2068    2013-01-14 16:45:38
2066    2013-01-14 16:45:38
parent continue
2066    2013-01-14 16:45:40
child start, pid 2069
2069    2067    2013-01-14 16:45:40
2013-01-14 16:45:40
2068    2013-01-14 16:45:40
2066    2013-01-14 16:45:41
2069    2013-01-14 16:45:41
2068    2013-01-14 16:45:41
signel 17 received
         child end pid 2067, status 0
2069    2013-01-14 16:45:42
2068    2013-01-14 16:45:42
2069    2013-01-14 16:45:43
signel 17 received
         child end pid 2068, status 0
2069    2013-01-14 16:45:44
signel 17 received
         child end pid 2069, status 0

多个进程又并行运行了,而且运行大约10秒钟之后,用 ps -ef | grep php 查看正在运行的进程,只有一个进程
lqling    2066  1388  0 16:45 pts/1    00:00:00 php -f t5.php
是父进程,子进程被回收了。


4. 子进程退出状态

pcntl_waitpid(-1, $status, WNOHANG) $status 返回子进程的结束状态


5. windows多线程

windows系统不支持pcntl函数,幸好有curl_multi_exec()这个工具,利用内部的多线程,访问多个链接,每个链接可以作为一个任务。

编写脚本 test1.php
<?php
    date_default_timezone_set( 'Asia/Chongqing');
    $tasks = array(
         'http://localhost/feedbowl/t2.php?job=task1',
         'http://localhost/feedbowl/t2.php?job=task2',
         'http://localhost/feedbowl/t2.php?job=task3'
    );
    $mh = curl_multi_init();
    foreach ($tasks as $i => $task){
         $ch[$i] = curl_init();
         curl_setopt($ch[$i], CURLOPT_URL, $task);
         curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1);
         curl_multi_add_handle($mh, $ch[$i]);
    }
    do {$mrc = curl_multi_exec($mh,$active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);
    while ($active && $mrc == CURLM_OK) {
         if (curl_multi_select($mh) != -1) {
           do {$mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);
         }
    }
    // completed, checkout result
    foreach ($tasks as $j => $task){
         if (curl_error($ch[$j])){
              echo "task ${j} [$task ] error " , curl_error($ch[$j]), "\r\n" ;
         } else {
              echo "task ${j} [$task ] get: \r\n" , curl_multi_getcontent($ch[$j]), "\r\n" ;
         }
    }
?>
编写脚本 test2.php
<?php
    date_default_timezone_set( 'Asia/Chongqing');
    echo "child start, pid ", getmypid(), "\r\n" ;
    for ($i=0; $i<5; ++$i){
         beep();
    }
    exit (0);
    // ***
    function beep(){
        echo getmypid(), "\t" , date('Y-m-d H:i:s' , time()), "\r\n";
        sleep(1);
    }
?>
用命令行运行
#php -f test1.php &

输出结果
task 0 [http://localhost/feedbowl/t2.php?job=task1] get:
child start, pid 5804
5804    2013-01-15 20:22:35
5804    2013-01-15 20:22:36
5804    2013-01-15 20:22:37
5804    2013-01-15 20:22:38
5804    2013-01-15 20:22:39

task 1 [http://localhost/feedbowl/t2.php?job=task2] get:
child start, pid 5804
5804    2013-01-15 20:22:35
5804    2013-01-15 20:22:36
5804    2013-01-15 20:22:37
5804    2013-01-15 20:22:38
5804    2013-01-15 20:22:39

task 2 [http://localhost/feedbowl/t2.php?job=task3] get:
child start, pid 5804
5804    2013-01-15 20:22:35
5804    2013-01-15 20:22:36
5804    2013-01-15 20:22:37
5804    2013-01-15 20:22:38
5804    2013-01-15 20:22:39

从打印的时间看到,多个任务几乎是同时运行的。


你可能感兴趣的:(多线程,windows,PHP,多进程,pcntl扩展)