本文將介紹PHP子進程的使用,使用linux消息隊列機制來達成進程間的協作,最後用一個簡單的例子來類比具體應用方案。
1. 子進程
1.1 創建子進程
int pcntl_fork ( void )
按照php官方的說明,pcntl_fork()函數會創建一個子進程,這個子進程僅PID(進程號) 和PPID(父進程號)與其父進程不同。成功時,在父進程執行線程內返回產生的子進程的PID,在子進程執行線程內返回0。失敗時,在父進程上下文返回-1,不會創建子進程,並且會引發一個PHP錯誤。
fork之後,操作系統會複製一個與父進程近似的子進程,這2個進程共享代碼空間,但是數據空間是互相獨立的,子進程數據空間中的內容是父進程的完整拷貝,指令指針也完全相同,只有對於fork調用的返回會在父子進程中產生區別(見上)。簡單理解就是fork之後,會產生一個新的子進程執行之後的代碼。
至於哪一個進程最先運行,這與操作系統平臺的調度算法有關,而且這個問題在實際應用中並不重要,對於父子進程協同運作,我們將介紹消息隊列的使用。
1.2 結束子進程
當子進程處理完自己的任務,需要直接結束時,我們直接關閉該進程即可:
int posix_getpid ( void )
使用該函數,返回進程 id 號,爲整型(integer)。在子進程中,該函數返回的即是子進程的進程號。
bool posix_kill ( int $pid , int $sig)
傳遞sig信號給pid對應的進程,關閉這個進程。sig參數可以百度“PHP信號管理”擴展閱讀,這裏我們使用簡單的SIGTERM爲終止進程用的軟件終止信號。而pid使用剛剛介紹的獲取進程id的函數即可獲取。
2. 消息隊列
2.1 創建消息隊列
int ftok ( string $pathname , string $proj)
將一個可訪問的文件路徑名轉換爲一個可供 shmop_open() (此函數用於生產共享內存空間,即我們的消息隊列)和其他系統VIPC keys使用的整數,proj參數必須是一個字符串,這個參數其實就是讀寫方式,一般使用a即可。
使用該函數,我們就可以簡單生成一個消息隊列用的鍵值。
resource msg_get_queue ( int $key [,int $perms = 0666 ] )
msg_get_queue()會根據傳入的鍵值(即使用上一個函數獲取的)返回一個消息隊列的引用。如果linux系統中沒有消息隊列與鍵值對應,msg_get_queue()將會創建一個新的消息隊列。
函數的第二個參數需要傳入一個int值,作爲新創建的消息隊列的權限值,默認爲0666。這個權限值與linux命令chmod中使用的數值是一致的。
該函數的返回值是一個用於訪問消息隊列的句柄。
2.2 發送消息
bool msg_send ( resource $queue , int $msgtype , mixed $message)
實際定義還有一些參數可以傳,由於比較冷門就不介紹了。這裏queue對應消息隊列的句柄。msgtype涉及到接收消息時候的操作,如果msgtype=0,接收消息隊列的第一個消息;大於0接收隊列中消息類型等於這個值的第一個消息;小於0接收消息隊列中小於或者等於msgtype絕對值的所有消息中的最小一個消息。最後的message是傳遞的消息。
2.3 接受消息
bool msg_receive ( resource $queue , int $desiredmsgtype ,int &$msgtype , int $ maxsize, mixed &$message [, bool $unserialize =true [, int $flags = 0 [, int &$errorcode ]]] )
同樣是非常長的一個函數,參數涉及到:
queue對應消息隊列的句柄。
desiredmsgtype定義和發送用的msytype類似,只是0表示消息隊列中的首個消息會被接受。
msgtype接受到消息的類型
maxsize接受消息的最大長度,如果消息長度超過這個限制,那麼獲取消息就會失敗。
message消息本身,使用引用的方式獲取值。
unserialize爲true的情況下,不會對收到的信息進行二值化,也就是可以收到其他php腳本發來的數組或是複雜結構;爲false的情況下,返回的將是字符串。
Flags函數的處理方式。MSG_IPC_NOWAIT,直接返回,如果失敗則返回一個MSG_ENOMSG的整數。 MSG_EXCEPT,使用後當desiredmsgtype大於0 時,可獲取第一條不爲desiredmsgtype的消息。MSG_NOERROR如果消息超過了最大長度,會進行截斷並不返回錯誤。
3. 實例
介紹了這麼多函數,其實根本就不知道該怎麼用,這裏設計了一個比較貼近生產環境的流程:
在父進程中,我們創建了5個子進程來一起處理任務,例子中是簡單的echo,實際中可能是統計某日的數據,或者是計算用戶答案是否正確。之後每個子進程會不斷讀取消息隊列中的消息,在處理到一定數量的消息後,結束這個子進程。而父進程在創建完子進程後,只需要不斷的在消息隊列中寫入需要處理的任務即可。
<?php
$message_queue_key= ftok(__FILE__, 'a');
$message_queue= msg_get_queue($message_queue_key, 0666);
$pids= array();
for( $i = 0; $i < 5; $i++) {
$pids[$i] = pcntl_fork();
if ($pids[$i]) {
echo "No.$i child process wascreated, the pid is $pids[$i]\r\n";
} elseif ($pids[$i] == 0) {
$pid = posix_getpid();
echo "process.$pidstart\r\n";
$count = 0;
do {
msg_receive($message_queue, 0,$message_type, 1024, $message, true, MSG_IPC_NOWAIT);
echo "process.$pid dealmessage{$message}\r\n";
$count++;
if($count == 5) {
break;
}
sleep(1);
} while (true);
echo "process.$pid end\r\n";
posix_kill($pid, SIGTERM);
}
}
for( $i = 0; $ i< 25; $i++) {
msg_send($message_queue, 1,rand(1000,10000));
}
?>
處理的log如下,由於例子的輸出有點長,這裏選取主要的部分。可以看到子進程會如期接收到父進程寫的任務,並且各自同時完成執行。
No.0 child process was created, thepid is 26512
process.26512 start
process.26512 deal message2513
No.1 child process was created, thepid is 26513
……
process.26513 deal message4692
process.26512 deal message5297
process.26512 end
……