php的多进程处理依赖于pcntl扩展,通过pcntl_fork创建子进程来进行并行处理
例子1:
1 <?php 2 $pid = pcntl_fork(); 3 4 if($pid == -1) { 5 //错误处理:创建子进程失败时返回-1. 6 die('fork error'); 7 } else if ($pid) { 8 //父进程会得到子进程号,所以这里是父进程执行的逻辑 9 echo "parent "; 10 //等待子进程中断,防止子进程成为僵尸进程。 11 pcntl_wait($status); 12 } else { 13 //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。 14 echo "child "; 15 16 exit; 17 }
pcntl_fork创建了子进程,父进程和子进程都继续向下执行,而不同是父进程会获取子进程的$pid也就是$pid不为零。而子进程会获取$pid为零。通过if else语句判断$pid我们就可以在指定位置写上不同的逻辑代码。
上述代码会分别输出parent和child。那么输出的parent和child是否会有顺序之分?是父进程会先执行?
例子2:
1 <?php 2 $pid = pcntl_fork(); 3 4 if($pid == -1) { 5 die('fork error'); 6 } else if ($pid) { 7 sleep(3); 8 echo "parent "; 9 pcntl_wait($status); 10 } else { 11 echo "child "; 12 13 exit; 14 }
很快输出了child,等待了接近3秒后,才输出parent。所以父进程和子进程的执行是相对独立的,没有先后之分。
那么问题又来了?pcntl_wait是做什么用的?
会挂起当前进程,直到子进程退出,如果子进程在调用此函数之前就已退出,此函数会立刻返回。子进程使用的资源将被释放。
例子3:
1 <?php 2 $pid = pcntl_fork(); 3 4 if($pid == -1) { 5 die('fork error'); 6 } else if ($pid) { 7 pcntl_wait ($status); 8 echo "parent "; 9 } else { 10 sleep(3); 11 echo "child "; 12 13 exit; 14 }
我们可以看到,父进程执行pcntl_wait时就已经挂起,直到等待3秒后输出child,子进程退出后。父进程继续执行,输出parent。
例子4:
1 <?php 2 define('FORK_NUMS', 3); 3 4 $pids = array(); 5 6 for($i = 0; $i < FORK_NUMS; ++$i) { 7 $pids[$i] = pcntl_fork(); 8 if($pids[$i] == -1) { 9 die('fork error'); 10 } else if ($pids[$i]) { 11 pcntl_waitpid($pids[$i], $status); 12 echo "pernet "; 13 } else { 14 sleep(3); 15 echo "child id:" . getmypid() . " "; 16 exit; 17 } 18 }
我们创建3个子进程,父进程分别挂起等待子进程结束后,输出parent。
输出结果如下:
例子5:
1 <?php 2 define('FORK_NUMS', 3); 3 4 $pids = array(); 5 6 for($i = 0; $i < FORK_NUMS; ++$i) { 7 $pids[$i] = pcntl_fork(); 8 if($pids[$i] == -1) { 9 die('fork error'); 10 } else if ($pids[$i]) { 11 12 } else { 13 sleep(3); 14 echo "child id:" . getmypid() . " "; 15 exit; 16 } 17 } 18 19 foreach($pids as $k => $v) { 20 if($v) { 21 pcntl_waitpid($v, $status); 22 echo "parent "; 23 } 24 }
输出结果:
为什么上述代码跟例4的输出结果不一样?
我们可以看到例5的pcntl_waitpid函数放在了foreach中,foreach代码是在主进程中,也就是父进程的代码中。当执行foreach时,可能子进程已经全部执行完毕并退出。pcntl_waitpid会立刻返回,连续输出三个parent。
(*在子进程中,需通过exit来退出,不然会产生递归多进程,父进程中不需要exit,不然会中断多进程。)
例子6:
1 <?php 2 3 define('FORK_NUMS', 3); 4 5 $pids = array(); 6 7 $fp = fopen('./test.log', 'wb'); 8 $num = 1; 9 10 for($i = 0; $i < FORK_NUMS; ++$i) { 11 $pids[$i] = pcntl_fork(); 12 if($pids[$i] == -1) { 13 die('fork error'); 14 } else if ($pids[$i]) { 15 16 17 } else { 18 for($i = 0; $i < 5; ++$i) { 19 20 flock($fp, LOCK_EX); 21 fwrite($fp, getmypid() . ' : ' . date('Y-m-d H:i:s') . " : {$num} "); 22 23 flock($fp, LOCK_UN); 24 echo getmypid(), ": success "; 25 ++$num; 26 } 27 exit; 28 } 29 } 30 31 foreach($pids as $k => $v) { 32 if($v) { 33 pcntl_waitpid($v, $status); 34 } 35 } 36 37 fclose($fp);
可以看到三个子进程的pid,它们分别执行了5次,时间几乎是在同时。但是$num的值并没像我们期望的那样从1-15进行递增。子进程中的变量是各自独立的,互不影响。子进程会自动复制父进程空间里的变量。
如何在进程中共享数据?
我们通过php的共享内存函数shmop来实现。
例子7:
1 <?php 2 3 define('FORK_NUMS', 3); 4 5 $pids = array(); 6 7 $fp = fopen('./test.log', 'wb'); 8 $num = 1; 9 //共享内存段的key 10 $shmKey = 123; 11 //创建共享内存段 12 $shmId = shmop_open($shmKey, 'c', 0777, 64); 13 //写入数据到共享内存段 14 shmop_write($shmId, $num, 0); 15 16 for($i = 0; $i < FORK_NUMS; ++$i) { 17 $pids[$i] = pcntl_fork(); 18 if($pids[$i] == -1) { 19 die('fork error'); 20 } else if ($pids[$i]) { 21 22 //阻塞,等待子进程退出 23 24 //注意这里,如果是非阻塞的话,$num的计数会出现问题。 25 pcntl_waitpid($pids[$i], $status); 26 } else { 27 //读取共享内存段中的数据 28 $num = shmop_read($shmId, 0, 64); 29 for($i = 0; $i < 5; ++$i) { 30 fwrite($fp, getmypid() . ' : ' . date('Y-m-d H:i:s') . " : {$num} "); 31 echo getmypid(), ": success "; 32 //递增$num 33 $num = intval($num) + 1; 34 } 35 36 //写入到共享内存段中 37 38 shmop_write($shmId, $num, 0); 39 exit; 40 } 41 } 42 43 //shmop_delete不会实际删除该内存段,它将该内存段标记为删除。 44 shmop_delete($shmId); 45 shmop_close($shmId); 46 fclose($fp);
最后结果:
这样我们就在进程间共享了$num的数据