昨天组内同学在使用php父子进程模式的时候遇到了一个比较诡异的问题
简单说来就是:因为fork,父子进程共享了一个redis连接、然后父子进程在发送了各自的redis请求分别获取到了对方的响应体。
复现示例代码:
testFork.php
1 <?php 2 require_once("./PowerSpawn.php"); 3 4 $ps = new Forkutil_PowerSpawn(); 5 $ps->maxChildren = 10 ; 6 $ps->timeLimit = 86400; 7 8 $redisObj = new Redis(); 9 $redisObj->connect('127.0.0.1','6379'); 10 11 // 主进程 -- 查询任务列表并新建子进程 12 while ($ps->runParentCode()) { 13 echo "parent:".$redisObj->get("parent")."\n" ; 14 // 产生一个子进程 15 if ($ps->spawnReady()) { 16 $ps->spawnChild(); 17 } else { 18 // 队列已满,等待 19 $ps->Tick(); 20 } 21 } 22 23 // 子进程 -- 处理具体的任务 24 if ($ps->runChildCode()) { 25 echo "chlidren:".$redisObj->get("children")."\n" ; 26 }
PowerSpawn.php 主要用户进程fork管理工作
<?php /* * PowerSpawn * * Object wrapper for handling process forking within PHP * Depends on PCNTL package * Depends on POSIX package * * Author: Don Bauer * E-Mail: lordgnu@me.com * * Date: 2011-11-04 */ declare(ticks = 1); class Forkutil_PowerSpawn { private $myChildren; private $parentPID; private $shutdownCallback = null; private $killCallback = null; public $maxChildren = 10; // Max number of children allowed to Spawn public $timeLimit = 0; // Time limit in seconds (0 to disable) public $sleepCount = 100; // Number of uSeconds to sleep on Tick() public $childData; // Variable for storage of data to be passed to the next spawned child public $complete; public function __construct() { if (function_exists('pcntl_fork') && function_exists('posix_getpid')) { // Everything is good $this->parentPID = $this->myPID(); $this->myChildren = array(); $this->complete = false; // Install the signal handler pcntl_signal(SIGCHLD, array($this, 'sigHandler')); } else { die("You must have POSIX and PCNTL functions to use PowerSpawn\n"); } } public function __destruct() { } public function sigHandler($signo) { switch ($signo) { case SIGCHLD: $this->checkChildren(); break; } } public function getChildStatus($name = false) { if ($name === false) return false; if (isset($this->myChildren[$name])) { return $this->myChildren[$name]; } else { return false; } } public function checkChildren() { foreach ($this->myChildren as $i => $child) { // Check for time running and if still running if ($this->pidDead($child['pid']) != 0) { // Child is dead unset($this->myChildren[$i]); } elseif ($this->timeLimit > 0) { // Check the time limit if (time() - $child['time'] >= $this->timeLimit) { // Child had exceeded time limit $this->killChild($child['pid']); unset($this->myChildren[$i]); } } } } /** * 获取当前进程pid * @return int */ public function myPID() { return posix_getpid(); } /** * 获取父进程pid * @return int */ public function myParent() { return posix_getppid(); } /** * 创建子进程 并记录到myChildren中 * @param bool $name */ public function spawnChild($name = false) { $time = time(); $pid = pcntl_fork(); if ($pid) { if ($name !== false) { $this->myChildren[$name] = array('time'=>$time,'pid'=>$pid); } else { $this->myChildren[] = array('ti