深入探究PHP的多进程编程方法

子进程的创建一般的子进程的写法是:

php$pid = pcntl_fork();if($pid == -1){ //创建失败 die('could not fork');}else{ if($pid){ //从这里开始写的代码是父进程的 exit("parent!"); } else{ //子进程代码,为防止不停的启用子进程造成系统资源被耗尽的情况,一般子进程代码运行完成后,加入exit来确保子进程正常退出。 exit("child"); }}

上边的代码如果创建子进程成功的话,系统就有了2个进程,一个为父进程,一个为子进程,子进程的id号为$pid。在系统运行到$pid

pcntl_fork();时,在这个地方进行分支,父子进程各自开始运行各自的程序代码。代码的运行结果是parent
和child,很奇怪吧,为什么一个if和else互斥的代码中,都输出了结果?其实是像上边所说的,代码在pcntl_fork时,一个父进程运行parent,一个子进程运行了child。在代码结果上就显示了parent和child。至于谁先谁后的问题,这得要看系统资源的分配了。

如果需要起多个进程来处理数据,可以根据数据的数量,按照约定好的数量比如说1000条一个进程来起子进程。使用for循环就可以了。

 #如果获得的总数小于或等于0,等待60秒,并退出 if ($count = 0) { sleep(60); exit; } #如果大于1000,计算需要起的进程数 if ($count  1000) { $cycleSize = ceil($count/1000); } else { $cycleSize = 1; } for ($i=0; $i$cycleSize; $i++) { $pid = pcntl_fork(); if($pid == -1) { break; } else { if($pid) { #父进程获得子进程的pid,存入数组 $pidArr[] = $pid; } else { //开始发送,子进程执行完自己的任务后,退出。 exit; } } } while(count($pidArr)  0) { $myId = pcntl_waitpid(-1, $status, WNOHANG); foreach($pidArr as $key = $pid) { if($myId == $pid) unset($pidArr[$key]); } }

然后使用crontab,来使此PHP程序每隔一段时间自动执行。

当然,示例代码比较简单,具体还需要考虑怎么防止多个子进程执行到同一条数据或者当前进程处理数据未完成时,crontab又开始执行PHP文件启用新的进程等等。

PHP多进程实现方式下面来系统地整理一下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; $i3; ++$i){ $pid = pcntl_fork(); if ($pid == -1){ die ("cannot fork" ); } else if ($pid  0){ echo "parent continue /n"; for ($k=0; $k2; ++$k){ beep(); } } else if ($pid == 0){ echo "child start, pid ", getmypid(), "/n" ; for ($j=0; $j5; ++$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 17931793 2013-01-14 15:04:17parent continue1793 2013-01-14 15:04:18child start, pid 17941794 2013-01-14 15:04:181794 2013-01-14 15:04:191793 2013-01-14 15:04:191794 2013-01-14 15:04:20parent continue1793 2013-01-14 15:04:20child start, pid 17951795 2013-01-14 15:04:2017931794 2013-01-14 15:04:212013-01-14 15:04:211795 2013-01-14 15:04:211794 2013-01-14 15:04:221795 2013-01-14 15:04:22parent continue1793 2013-01-14 15:04:22child start, pid 17961796 2013-01-14 15:04:221793 2013-01-14 15:04:231796 2013-01-14 15:04:231795 2013-01-14 15:04:231795 2013-01-14 15:04:241796 2013-01-14 15:04:241796 2013-01-14 15:04:251796 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; $k2; ++$k){ beep(); }} else if ($pid == 0){ ...}

澳门新葡亰网站注册,用命令行运行

#php -f test.php

输出结果

parent start, pid 18071807 2013-01-14 15:20:05parent continuechild start, pid 18081808 2013-01-14 15:20:061808 2013-01-14 15:20:071808 2013-01-14 15:20:081808 2013-01-14 15:20:091808 2013-01-14 15:20:101807 2013-01-14 15:20:111807 2013-01-14 15:20:12parent continuechild start, pid 18091809 2013-01-14 15:20:131809 2013-01-14 15:20:141809 2013-01-14 15:20:151809 2013-01-14 15:20:161809 2013-01-14 15:20:171807 2013-01-14 15:20:181807 2013-01-14 15:20:19child start, pid 18101810 2013-01-14 15:20:20parent continue1810 2013-01-14 15:20:211810 2013-01-14 15:20:221810 2013-01-14 15:20:231810 2013-01-14 15:20:241807 2013-01-14 15:20:251807 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; $i3; ++$i){ $pid = pcntl_fork(); if ($pid == -1){ die ("cannot fork" ); } else if ($pid  0){ echo "parent continue /n"; for ($k=0; $k2; ++$k){ beep(); } } else if ($pid == 0){ echo "child start, pid ", getmypid(), "/n" ; for ($j=0; $j5; ++$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 20662066 2013-01-14 16:45:34parent continue2066 2013-01-14 16:45:35child start, pid 20672067 2013-01-14 16:45:3520662067 2013-01-14 16:45:362013-01-14 16:45:362067 2013-01-14 16:45:37parent continue2066 2013-01-14 16:45:37child start, pid 20682068 2013-01-14 16:45:372067 2013-01-14 16:45:382068 2013-01-14 16:45:382066 2013-01-14 16:45:38parent continue2066 2013-01-14 16:45:40child start, pid 20692069 2067 2013-01-14 16:45:402013-01-14 16:45:402068 2013-01-14 16:45:402066 2013-01-14 16:45:412069 2013-01-14 16:45:412068 2013-01-14 16:45:41signel 17 received child end pid 2067, status 02069 2013-01-14 16:45:422068 2013-01-14 16:45:422069 2013-01-14 16:45:43signel 17 received child end pid 2068, status 02069 2013-01-14 16:45:44signel 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是父进程,子进程被回收了。

子进程退出状态

pcntl_waitpid(-1, $status, WNOHANG) $status

返回子进程的结束状态

windows下多线程

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

编写脚本 test1.php

php date_default_timezone_set( 'Asia/Chongqing'); $tasks = array( '', '', '' ); $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; $i5; ++$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 [] get:child start, pid 58045804 2013-01-15 20:22:355804 2013-01-15 20:22:365804 2013-01-15 20:22:375804 2013-01-15 20:22:385804 2013-01-15 20:22:39task 1 [] get:child start, pid 58045804 2013-01-15 20:22:355804 2013-01-15 20:22:365804 2013-01-15 20:22:375804 2013-01-15 20:22:385804 2013-01-15 20:22:39task 2 [] get:child start, pid 58045804 2013-01-15 20:22:355804 2013-01-15 20:22:365804 2013-01-15 20:22:375804 2013-01-15 20:22:385804 2013-01-15 20:22:39

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