子进程的创建
一般的子进程的写法是:
<?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 processesdate_default_timezone_set( "Asia/Chongqing");echo "parent start, pid ", getmypid(), "
" ;beep();for ($i=0; $i<3; ++$i){ $pid = pcntl_fork();if ($pid == -1){ die ("cannot fork" ); } else if ($pid > 0){ echo "parent continue
"; for ($k=0; $k<2; ++$k){ beep();} } else if ($pid == 0){ echo "child start, pid ", getmypid(), "
" ; for ($j=0; $j<5; ++$j){ beep();} exit ; }}// ***function beep(){echo getmypid(), " " , date( "Y-m-d H:i:s", time()), "
" ; sleep(1);}?>用命令行运行
#php -f test.php
输出结果
parent start, pid 179317932013-01-14 15:04:17parent continue17932013-01-14 15:04:18child start, pid 179417942013-01-14 15:04:1817942013-01-14 15:04:1917932013-01-14 15:04:1917942013-01-14 15:04:20parent continue17932013-01-14 15:04:20child start, pid 179517952013-01-14 15:04:20179317942013-01-14 15:04:212013-01-14 15:04:2117952013-01-14 15:04:2117942013-01-14 15:04:2217952013-01-14 15:04:22parent continue17932013-01-14 15:04:22child start, pid 179617962013-01-14 15:04:2217932013-01-14 15:04:2317962013-01-14 15:04:2317952013-01-14 15:04:2317952013-01-14 15:04:2417962013-01-14 15:04:2417962013-01-14 15:04:2517962013-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
"; pcntl_wait($status); for ($k=0; $k<2; ++$k){ beep();}} else if ($pid == 0){ ...}用命令行运行
#php -f test.php
输出结果
parent start, pid 180718072013-01-14 15:20:05parent continuechild start, pid 180818082013-01-14 15:20:0618082013-01-14 15:20:0718082013-01-14 15:20:0818082013-01-14 15:20:0918082013-01-14 15:20:1018072013-01-14 15:20:1118072013-01-14 15:20:12parent continuechild start, pid 180918092013-01-14 15:20:1318092013-01-14 15:20:1418092013-01-14 15:20:1518092013-01-14 15:20:1618092013-01-14 15:20:1718072013-01-14 15:20:1818072013-01-14 15:20:19child start, pid 181018102013-01-14 15:20:20parent continue18102013-01-14 15:20:2118102013-01-14 15:20:2218102013-01-14 15:20:2318102013-01-14 15:20:2418072013-01-14 15:20:2518072013-01-14 15:20:26
父进程在pcntl_wait()将自己阻塞,等待子进程运行完了才接着运行。
3. 非阻塞方式阻塞方式失去了多进程的并行性。还有一种方法,既可以回收已经结束的子进程,又可以并行。这就是非阻塞的方式。
修改脚本:
<?php// example of multiple processesdate_default_timezone_set( "Asia/Chongqing");declare (ticks = 1);pcntl_signal(SIGCHLD, "garbage" );echo "parent start, pid ", getmypid(), "
" ;beep();for ($i=0; $i<3; ++$i){ $pid = pcntl_fork();if ($pid == -1){ die ("cannot fork" ); } else if ($pid > 0){ echo "parent continue
"; for ($k=0; $k<2; ++$k){ beep();} } else if ($pid == 0){ echo "child start, pid ", getmypid(), "
" ; for ($j=0; $j<5; ++$j){ beep();} exit (0); }}// parentwhile (1){// do something else sleep(5);}// ***function garbage($signal){echo "signel $signal received
" ;while (($pid = pcntl_waitpid(-1, $status, WNOHANG))> 0){ echo " child end pid $pid , status $status
" ; }}function beep(){echo getmypid(), " " , date( "Y-m-d H:i:s", time()), "
" ; sleep(1);}?>用命令行运行
#php -f test.php &
输出结果
parent start, pid 206620662013-01-14 16:45:34parent continue20662013-01-14 16:45:35child start, pid 206720672013-01-14 16:45:35206620672013-01-14 16:45:362013-01-14 16:45:3620672013-01-14 16:45:37parent continue20662013-01-14 16:45:37child start, pid 206820682013-01-14 16:45:3720672013-01-14 16:45:3820682013-01-14 16:45:3820662013-01-14 16:45:38parent continue20662013-01-14 16:45:40child start, pid 2069206920672013-01-14 16:45:402013-01-14 16:45:4020682013-01-14 16:45:4020662013-01-14 16:45:4120692013-01-14 16:45:4120682013-01-14 16:45:41signel 17 received child end pid 2067, status 020692013-01-14 16:45:4220682013-01-14 16:45:4220692013-01-14 16:45:43signel 17 received child end pid 2068, status 020692013-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
<?phpdate_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 resultforeach ($tasks as $j => $task){ if (curl_error($ch[$j])){ echo "task ${j} [$task ] error " , curl_error($ch[$j]), "
" ; } else { echo "task ${j} [$task ] get:
" , curl_multi_getcontent($ch[$j]), "
" ; }}?>编写脚本 test2.php
<?phpdate_default_timezone_set( "Asia/Chongqing");echo "child start, pid ", getmypid(), "
" ;for ($i=0; $i<5; ++$i){ beep();}exit (0);// ***function beep(){echo getmypid(), " " , date("Y-m-d H:i:s" , time()), "
";sleep(1);}?>用命令行运行
#php -f test1.php &
输出结果
task 0 [http://localhost/feedbowl/t2.php?job=task1] get:child start, pid 580458042013-01-15 20:22:3558042013-01-15 20:22:3658042013-01-15 20:22:3758042013-01-15 20:22:3858042013-01-15 20:22:39task 1 [http://localhost/feedbowl/t2.php?job=task2] get:child start, pid 580458042013-01-15 20:22:3558042013-01-15 20:22:3658042013-01-15 20:22:3758042013-01-15 20:22:3858042013-01-15 20:22:39task 2 [http://localhost/feedbowl/t2.php?job=task3] get:child start, pid 580458042013-01-15 20:22:3558042013-01-15 20:22:3658042013-01-15 20:22:3758042013-01-15 20:22:3858042013-01-15 20:22:39
从打印的时间看到,多个任务几乎是同时运行的。