最近在熟悉PHP CLI下的多进程编程,由于非科班出身没有扎实的计算机基础,学起来尤其吃力,今天对这些天的学习做一些总结吧
先贴一段从php.net得到的代码
针对pcntl_fork()一点补充说明
在pcntl_fork函数中,系统会复制父进程的数据段和堆栈段,但是至于父进程共享代码段。所以并非在返回后才产生两个进程,而是先产生了子进程然后,父进程和子进程分别从栈中返回反回,此时已经是两个进程了,就是说pcntl_fork()返回两次,所以下边的针对pid的判断就有意义了。
Using pcntl_fork() can be a little tricky in some situations. For fast jobs, a child can finish processing before the parent process has executed some code related to the launching of the process. The parent can receive a signal before it's ready to handle the child process' status. To handle this scenario, I add an id to a "queue" of processes in the signal handler that need to be cleaned up if the parent process is not yet ready to handle them.
I am including a stripped down version of a job daemon that should get a person on the right track.
<?php
declare(ticks=1);
//A very basic job daemon that you can extend to your needs.
class JobDaemon{
public $maxProcesses = 25;
protected $jobsStarted = 0;
protected $currentJobs = array();
protected $signalQueue=array();
protected $parentPID;
public function __construct(){
echo "constructed \n";
$this->parentPID = getmypid();
pcntl_signal(SIGCHLD, array($this, "childSignalHandler"));
}
/**
* Run the Daemon
*/
public function run(){
echo "Running \n";
for($i=0; $i<10000; $i++){
$jobID = rand(0,10000000000000);
while(count($this->currentJobs) >= $this->maxProcesses){
echo "Maximum children allowed, waiting...\n";
sleep(1);
}
$launched = $this->launchJob($jobID);
}
//Wait for child processes to finish before exiting here
while(count($this->currentJobs)){
echo "Waiting for current jobs to finish... \n";
sleep(1);
}
}
/**
* Launch a job from the job queue
*/
protected function launchJob($jobID){
$pid = pcntl_fork();
if($pid == -1){
//Problem launching the job
error_log('Could not launch new job, exiting');
return false;
}
else if ($pid){
// Parent process
// Sometimes you can receive a signal to the childSignalHandler function before this code executes if
// the child script executes quickly enough!
//
$this->currentJobs[$pid] = $jobID;
// In the event that a signal for this pid was caught before we get here, it will be in our signalQueue array
// So let's go ahead and process it now as if we'd just received the signal
if(isset($this->signalQueue[$pid])){
echo "found $pid in the signal queue, processing it now \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset($this->signalQueue[$pid]);
}
}
else{
//Forked child, do your deeds....
$exitStatus = 0; //Error code if you need to or whatever
echo "Doing something fun in pid ".getmypid()."\n";
exit($exitStatus);
}
return true;
}
public function childSignalHandler($signo, $pid=null, $status=null){
//If no pid is provided, that means we're getting the signal from the system. Let's figure out
//which child process ended
if(!$pid){
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
//Make sure we get all of the exited children
while($pid > 0){
if($pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if($exitCode != 0){
echo "$pid exited with status ".$exitCode."\n";
}
unset($this->currentJobs[$pid]);
}
else if($pid){
//Oh no, our job has finished before this parent process could even note that it had been launched!
//Let's make note of it and handle it when the parent process is ready for it
echo "..... Adding $pid to the signal queue ..... \n";
$this->signalQueue[$pid] = $status;
}
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
return true;
}
}
子进程只执行从
else{
//Forked child, do your deeds....
$exitStatus = 0; //Error code if you need to or whatever
echo "Doing something fun in pid ".getmypid()."\n";
exit($exitStatus);
}
还是没整理清楚思路
贴一下应用的代码吧
#!/usr/bin/php
<?php
error_reporting(E_ERROR);
echo time();
declare(ticks=1);
require './pageParse.php';
//A very basic job daemon that you can extend to your needs.
class JobDaemon{
public $maxProcesses = 25;
protected $jobsStarted = 0;
protected $currentJobs = array();
protected $signalQueue=array();
protected $parentPID;
private $job=false;
private $succ=false;
public function __construct(){
echo "constructed \n";
$semkey=6;
$shmkey=7;
$pageKey=8;
$threadKey=9;
$succKey=10;
$sem = sem_get($semkey);
if(sem_acquire($sem)){
$shm=shm_attach($shmkey);
shm_put_var($shm, $pageKey, 1);
shm_detach($shm);
$shm=shm_attach($threadKey);
shm_put_var($shm, $succKey, 0);
shm_detach($shm);
sem_release($sem);
}
$this->parentPID = getmypid();
pcntl_signal(SIGCHLD, array($this, "childSignalHandler"));
}
/**
* Run the Daemon
*/
public function run($job){
$this->job=$job;
echo "Running \n";
echo "\n";
var_dump(getmypid());
echo ':';
var_dump($this->succ);
echo "\n";
while(!$this->getSucc()){
$jobID = rand(0,10000000000000);
while(count($this->currentJobs) >= $this->maxProcesses){
echo "Maximum children allowed, waiting...\n";
sleep(1);
}
$launched = $this->launchJob($jobID);
}
//Wait for child processes to finish before exiting here
while(count($this->currentJobs)){
if(count($this->currentJobs)==1 && in_array($this->parentPID,$this->currentJobs)){
$command = "mysql -hm3346i.mars.grid.sina.com.cn -udataauto -pbS5i1hhpc -P3346 dataauto < pcntl.log &";
exec($command);
exit();
}
echo "Waiting for current jobs to finish... \n";
sleep(1);
echo time();
}
}
/**
* Launch a job from the job queue
*/
protected function launchJob($jobID){
$pid = pcntl_fork();
if($pid == -1){
//Problem launching the job
error_log('Could not launch new job, exiting');
return false;
}
else if ($pid){
// Parent process
// Sometimes you can receive a signal to the childSignalHandler function before this code executes if
// the child script executes quickly enough!
//
$this->currentJobs[$pid] = $jobID;
// In the event that a signal for this pid was caught before we get here, it will be in our signalQueue array
// So let's go ahead and process it now as if we'd just received the signal
if(isset($this->signalQueue[$pid])){
echo "found $pid in the signal queue, processing it now \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset($this->signalQueue[$pid]);
}
}
else{
//Forked child, do your deeds....
$exitStatus = 0; //Error code if you need to or whatever
echo "Doing something fun in pid ".getmypid()."\n";
$this->worker($this->job);
exit($exitStatus);
}
return true;
}
public function childSignalHandler($signo, $pid=null, $status=null){
//If no pid is provided, that means we're getting the signal from the system. Let's figure out
//which child process ended
if(!$pid){
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
//Make sure we get all of the exited children
while($pid > 0){
if($pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if($exitCode == 500){
$this->succ=true;
}
if($exitCode != 0){
echo "$pid exited with status ".$exitCode."\n";
}
unset($this->currentJobs[$pid]);
}
else if($pid){
//Oh no, our job has finished before this parent process could even note that it had been launched!
//Let's make note of it and handle it when the parent process is ready for it
echo "..... Adding $pid to the signal queue ..... \n";
$this->signalQueue[$pid] = $status;
}
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
return true;
}
public function worker($obj){
$semkey=6;
$shmkey=7;
$pageKey=8;
$threadKey=9;
$succKey=10;
$sem = sem_get($semkey);
if(sem_acquire($sem)){
$shm=shm_attach($shmkey);
$page=intval(trim(shm_get_var($shm,$pageKey)))+1;
var_dump($page);
$cpage=$page-1;
shm_put_var($shm, $pageKey, $page);
shm_detach($shm);
sem_release($sem);
}else{
echo 'echo return by no perm......';
}
if(isset($cpage)){
try{
$this->succ=$obj->run($cpage,25);
echo "\n";
var_dump(trim(getmypid()));
echo ':';
var_dump($this->succ);
echo "\n";
if($this->succ){
while(!sem_acquire($sem)){
}
$shm = shm_attach($threadKey);
shm_put_var($shm, $succKey, 1);
shm_detach($shm);
sem_release($sem);
}
}catch(Exception $e){
var_dump($e);
}
}
echo getmypid().'over working......';
return true;
}
private function getSucc(){
$semkey=6;
$shmkey=7;
$pageKey=8;
$threadKey=9;
$succKey=10;
$sem = sem_get($semkey);
while(!sem_acquire($sem)){ }
$shm = shm_attach($threadKey);
$tmp=shm_get_var($shm, $succKey);
shm_detach($shm);
sem_release($sem);
return $tmp;
}
}
$page = new pageParse('96ac1089-4b64-4dbf-8f00-6992a3dd8cc6','price');
$pc = new JobDaemon();
$pc -> run($page);
$page对象是一个获取公司接口的程序
进程间通信用到了共享内存
在linux下可以通过ipcs查看共享内存的使用情况通过ipcrm可删除共享内存
贴一个自己测试时用来删除共享内存的一个脚本,因为最开始程序错误申请了太多快内存,手动删除就太慢了不妨试试这个脚本
#!/bin/sh
sems=`ipcs -s|sed 's/Semaphore//'|cut -d " " -f2|grep -v '^
`for sem in $sems do ipcrm -s $sem doneshms=`ipcs -m|sed 's/Shared//'|cut -d " " -f2|grep -v '^
`for shm in $shmsdo ipcrm -m $shm done
又被declare困惑,这是这个进程控制器的关键所在,若没有他则信号队列会堵塞而不能处理,declare(ticks=1);则没执行一个简单指令就会重新运行后边的代码,于是主进程才能及时处理掉信号队列,由于从产生子进程到将子进程加入到进程队列时间可能比较长,在加入进程队列之前可能已经结束发出结束信号被放到信号队列等候处理,并且记录了对出码在信号处理函数中下边这段代码就是这个作用:
else if($pid){
//Oh no, our job has finished before this parent process could even note that it had been launched!
//Let's make note of it and handle it when the parent process is ready for it
echo "..... Adding $pid to the signal queue ..... \n";
$this->signalQueue[$pid] = $status;
}
于是加入进程队列后,检查是否有以上情况的进程退出出现在信号队列中并处理之,就是下边这段代码
if(isset($this->signalQueue[$pid])){
echo "found $pid in the signal queue, processing it now \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset($this->signalQueue[$pid]);
}
由于在信号处理函数中做了处理
if($pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if($exitCode != 0){
echo "$pid exited with status ".$exitCode."\n";
}
unset($this->currentJobs[$pid]);
}
就将信号队列中的提前退出等候的进程处理掉了,并在进程进程队列中删除此进程记录,然后又删除了信号队列中的此进程记录。
unset($this->signalQueue[$pid]);
经试验将此句放到信号处理函数中在删除任务队列记录后执行也可以
但通知主进程已经更新完毕时我用了共享内存,其实可以在信号处流函数中检测退出码
if($exitCode!=0){
echo "$pid exited with status ".$exitCode."\n";
}
来控制是否还需要产生子进程,在
$exitStatus = 0; //Error code if you need to or whatever
echo "Doing something fun in pid ".getmypid()."\n";
exit($exitStatus);
通过他的退出码就可以通知主进程不需要产生子进程了。
暂且,由于在理解declare时用到了opcode
可以通过
Parsekit Functions观察解析后的opcode
php -r "var_export(parsekit_compile_file('pcntl.php'));"
就可以观察opcode,这个opcode的list在手册中可以查到。
暂且写到这里,此片只是方便自己理解不方便拿来借鉴,带我整理清楚在写个正式版吧