PHP中的子進程和消息隊列

本文將介紹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
……


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章