使用PHP真正的多程序運作模式,适用于資料采集、郵件群發、資料源更新、tcp伺服器等環節。
PHP有一組程序控制函數(編譯時需要 –enable-pcntl與posix擴充),使得php能在*nix系統中實作跟c一樣的建立子程序、使用exec函數執行程式、處理信号等功能。PCNTL使用ticks來作為信号處理機制(signal handle callback mechanism),可以最小程度地降低處理異步事件時的負載。何謂ticks?Tick 是一個在代碼段中解釋器每執行 N 條低級語句就會發生的事件,這個代碼段需要通過declare來指定。
常用的PCNTL函數
- pcntl_alarm ( int $seconds )設定一個$seconds秒後發送SIGALRM信号的計數器
- pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] )為$signo設定一個處理該信号的回調函數。下面是一個隔5秒發送一個SIGALRM信号,并由signal_handler函數擷取,然後列印一個“Caught SIGALRM”的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
declare(ticks = 1);
function signal_handler($signal) {
print "Caught SIGALRM\n";
pcntl_alarm(5);
}
pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);
for(;;) {
}
?>
- pcntl_exec ( string $path [, array $args [, array $envs ]] )在目前的程序空間中執行指定程式,類似于c中的exec族函數。所謂目前空間,即載入指定程式的代碼覆寫掉目前程序的空間,執行完該程式程序即結束。
1
2
3
4
5
6
7
8
9
10
11
<?php
$dir = '/home/shankka/';
$cmd = 'ls';
$option = '-l';
$pathtobin = '/bin/ls';
$arg = array($cmd, $option, $dir);
pcntl_exec($pathtobin, $arg);
echo '123'; //不會執行到該行
?>
-
pcntl_fork ( void )為目前程序建立一個子程序,并且先運作父程序,傳回的是子程序的PID,肯定大于零。在父程序的代碼中可以用pcntl_wait(&$status)暫停父程序知道他的子程序有傳回值。注意:父程序的阻塞同時會阻塞子程序。但是父程序的結束不影響子程序的運作。
父程序運作完了會接着運作子程序,這時子程序會從執行pcntl_fork()的那條語句開始執行(包括此函數),但是此時它傳回的是零(代表這是一個子程序)。在子程序的代碼塊中最好有exit語句,即執行完子程序後立即就結束。否則它會又重頭開始執行這個腳本的某些部分。
注意兩點:
1. 子程序最好有一個exit;語句,防止不必要的出錯;
2. pcntl_fork間最好不要有其它語句,例如:
1
2
3
4
5
6
7
8
9
10
$pid = pcntl_fork();
//這裡最好不要有其他的語句
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// we are the parent
pcntl_wait($status); //Protect against Zombie children
} else {
// we are the child
}
-
pcntl_wait ( int &$status [, int $options ] )阻塞目前程序,隻到目前程序的一個子程序退出或者收到一個結束目前程序的信号。使用$status傳回子程序的狀态碼,并可以指定第二個參數來說明是否以阻塞狀态調用:
1. 阻塞方式調用的,函數傳回值為子程序的pid,如果沒有子程序傳回值為-1;
2. 非阻塞方式調用,函數還可以在有子程序在運作但沒有結束的子程序時傳回0。
-
pcntl_waitpid ( int $pid , int &$status [, int $options ] )功能同pcntl_wait,差別為waitpid為等待指定pid的子程序。當pid為-1時pcntl_waitpid與pcntl_wait一樣。在pcntl_wait和pcntl_waitpid兩個函數中的$status中存了子程序的狀态資訊,這個參數可以用于pcntl_wifexited、pcntl_wifstopped、pcntl_wifsignaled、pcntl_wexitstatus、pcntl_wtermsig、pcntl_wstopsig、pcntl_waitpid這些函數。
例如:
子程序在輸出child process等字樣之後sleep了2秒才結束,而父程序阻塞着直到子程序退出之後才繼續運作。1
2
3
4
5
6
7
8
9
10
11
12
<?php
$pid = pcntl_fork();
if($pid) {
pcntl_wait($status);
$id = getmypid();
echo "parent process,pid {$id}, child pid {$pid}\n";
}else{
$id = getmypid();
echo "child process,pid {$id}\n";
sleep(2);
}
?>
- pcntl_getpriority ([ int $pid [, int $process_identifier ]] )取得程序的優先級,即nice值,預設為0,在我的測試環境的linux中(CentOS release 5.2 (Final)),優先級為-20到19,-20為優先級最高,19為最低。(手冊中為-20到20)。
- pcntl_setpriority ( int $priority [, int $pid [, int $process_identifier ]] )設定程序的優先級。
- posix_kill可以給程序發送信号
- pcntl_singal用來設定信号的回調函數
當父程序退出時,子程序如何得知父程序的退出
當父程序退出時,子程序一般可以通過下面這兩個比較簡單的方法得知父程序已經退出這個消息:
- 當父程序退出時,會有一個INIT程序來領養這個子程序。這個INIT程序的程序号為1,是以子程序可以通過使用getppid()來取得目前父程序的pid。如果傳回的是1,表明父程序已經變為INIT程序,則原程序已經推出。
- 使用kill函數,向原有的父程序發送空信号(kill(pid, 0))。使用這個方法對某個程序的存在性進行檢查,而不會真的發送信号。是以,如果這個函數傳回-1表示父程序已經退出。
除了上面的這兩個方法外,還有一些實作上比較複雜的方法,比如建立管道或socket來進行時時的監控等等。
PHP多程序采集資料的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | <?php class Signfork{ private $tmp_path='/tmp/'; public function run($obj,$arg=1){ if(!method_exists($obj,'__fork')){ exit("Method '__fork' not found!"); } if(is_array($arg)){ $i=0; foreach($arg as $key=>$val){ $spawns[$i]=$key; $i++; $this->spawn($obj,$key,$val); } $spawns['total']=$i; }elseif($spawns=intval($arg)){ for($i = 0; $i < $spawns; $i++){ $this->spawn($obj,$i); } }else{ exit('Bad argument!'); } if($i>1000) exit('Too many spawns!'); return $this->request($spawns); } private function request($spawns){ $data=array(); $i=is_array($spawns)?$spawns['total']:$spawns; for($ids = 0; $ids<$i; $ids++){ while(!($cid=pcntl_waitpid(-1, $status, WNOHANG)))usleep(30000); $tmpfile=$this->tmp_path.'sfpid_'.$cid; $data[$spawns['total']?$spawns[$ids]:$ids]=file_get_contents($tmpfile); unlink($tmpfile); } return $data; } private function spawn($obj,$i,$param=null){ if(pcntl_fork()===0){ $cid=getmypid(); file_put_contents($this->tmp_path.'sfpid_'.$cid,$obj->__fork($param)); posix_kill($cid, SIGTERM); exit; } } } ?> |