php pcntl 實踐填坑

PHP 能夠經過pcntl 擴展實現多進程編程, 而網上關於如何經過pcntl 建立多進程的在這裏就不表了, 我主要說說關於pcntl_fork的一個坑和相關的比較生僻的幾個函數的使用方式, 這也是經過挖坑和填坑得出的結論。
閒言碎語不要講, 直接開始php

pcntl_fork

在實踐中, 我在使用php進行多進程實踐的模型大概以下, 期待的是每一個子進程都能建立一個與之對應文件, 最後父進程建立一個屬於父進程的文件,代碼以下(有坑):編程

$pid_dir = __dir__."/pid_files";
for($i=0; $i<3; $i++){
    $pid = pcntl_fork();
    if($pid == -1){
        var_dump("fork failed");
    }
    if(!$pid){
        //子進程代碼
        $pid = posix_getpid();
        $ppid = posix_getppid();
        $r = rand(0,100);  //隨機數
        touch("$pid_dir/fork_child_process_{$i}_{$ppid}_{$pid}_{$r}");
    }
} 
$pid = posix_getpid();
$ppid = posix_getppid();
$r = rand(0,100); //隨機數
touch("$pid_dir/fork_process_pid_{$ppid}_{$pid}_$r");

上面的代碼我經過循環建立3個子進程, 每一個進程建立一個文件,完成後到最後, 父進程建立一個屬於他本身的文件,因此, 最後應該會建立出4個文件, 但事實並不是如此:函數

fork_child_process_0_62656_62658_39
fork_child_process_1_62656_62659_51
fork_child_process_1_62658_62660_22
fork_child_process_2_62656_62661_91
fork_child_process_2_62658_62662_22
fork_child_process_2_62659_62663_82
fork_child_process_2_62660_62664_59
fork_process_pid_62225_62656_48
fork_process_pid_62656_62658_22
fork_process_pid_62656_62659_82
fork_process_pid_62656_62661_65
fork_process_pid_62658_62660_59
fork_process_pid_62658_62662_59
fork_process_pid_62659_62663_61
fork_process_pid_62660_62664_10

爲什麼會出現上面的結果, 這是由於在fork以後, 原有的進程會分裂爲兩個進程, 一個主進程, 一個子進程, fork後面全部的代碼都是共享的, 雖然經過fork的返回值能夠判斷是主進程仍是子進程來執行相應的子進程或主進程邏輯,但以後子進程本身又走到了for循環的部分, 子進程本身有建立了子進程, 因此上面看到了多個child_process 文件, 至於爲何是7個,
來分析一下。code

=====================華麗的分割線=============================繼承

循環變量$i, 當$i爲0時, 會產生一個主進程a(不變)和一個子進程aa,這個子進程建立了一個子進程文件,即fork_child_process_0_62656_62658_39, 主進程a繼續循環, 即$i=1, 又建立了一個子進程ab, 他建立了fork_child_process_1_62656_62659_51, 主進程a繼續循環$i=2, 又建立了一個子進程ac, 他建立了fork_child_process_2_62656_62661_91這裏能夠看到62656就是主進程a的pid.進程

至此, 主進程a的循環完畢, 在看看a建立的第一個子進程aa, aa在建立以後, 建立好了上面的子進程文件以後並不會什麼也不作, 他也會繼續走for的循環, 並且繼承了主進程a的循環變量, 也就是$i的值爲0,因此aa進程下一次的循環的$i就是1, 而後aa繼續建立了子進程aaa,從而建立文件fork_child_process_1_62658_62660_22aa繼續,$i=2, 又建立了一個子進程aab, 這個子進程建立了文件fork_child_process_2_62658_62662_22, 這裏能夠看到aaaaab的ppid就是aa的pid 62658,
同理aaa,aab 也繼承了aa的$i值,這時$i的值爲1, 當繼續循環時, $i 就變成了2, 也就只能循環一次了,相應aaaaab 建立了子進程文件fork_child_process_2_62659_62663_82(aaaa),fork_child_process_2_62660_62664_59(aaba),而他們相應的父進程就是aaa(62659)和aab(62660).get

至此, for循環中的多進程邏輯完成了, 也就是爲什麼產生了第一部分的7個文件it

=====================華麗的分割線=============================io

而至於爲什麼第二部分是8個文件, 各位能夠本身思考一下, 注意, 不管主進程仍是子進程, 在for循環完畢以後會繼續往下走, 知道這一點就好理解了。for循環

在實際的代碼中, 我就犯了這種錯誤。
那如何解決上面的問題呢, 只要在子進程執行的最後exit就好啦,

fork_child_process_0_63219_63221_66
fork_child_process_1_63219_63222_88
fork_child_process_2_63219_63223_22
fork_process_pid_62225_63219_77

繼續,那麼在網上看到不少多進程編程中使用pcntl_waitpid, 並不瞭解他是作什麼的,且相應的例子不多, 我暫且來講說個人理解

pcntl_waitpid

等待或返回fork的子進程狀態。
多進程的主進程建立了子進程,那主進程如何確認子進程的狀態呢。 假如主進程須要根據子進程的狀態作不一樣的處理呢, 這裏的狀態包括子進程被kill掉,或變成殭屍進程等。 pcntl_waitpid就能夠獲取子進程的狀態碼, 經過這個狀態碼, 就可知道子進程處於什麼狀態
他的用法:

int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )

返回的值能夠是-1,0或者 >0的值, 若是是-1, 表示子進程出錯, 若是>0表示子進程已經退出且值是退出的子進程pid,至於如何退出, 能夠經過$status狀態碼反應。 那何時返回0呢, 只有在option 參數爲 WNOHANG且子進程正在運行時0, 也就是說當設置了options=WNOHANG時, 若是子進程尚未退出, 此時pcntl_waitpid就會返回0
另外, 若是不設置這個參數爲WNOHANGpcntl_waitpid 就會阻塞運行, 直到子進程退出, 至於option的另一個值WUNTRACED, 暫未理解, 不表

那麼如何根據$status(狀態碼)判斷進程是如何退出呢, 以下(參數都是$status)

pcntl_wifexited

這個函數能夠根據$status 判斷進程是否正常退出, 何爲正常退出, 好比exit

pcntl_wexitstatus

這個函數僅在pcntl_wifexited 返回True(即正常退出)時有效, 且返回子進程退出的返回狀態碼, 這個返回狀態碼能夠經過exit($s)的參數($s必須爲整數時)定義

pcntl_wifsignaled

檢查子進程狀態碼是否表明因爲某個信號而中斷, 好比是否是咱們給他發送了term, int 等信號了

pcntl_wexitstatus

假如是發送信號而致使子進程中斷, 那麼這個信號是什麼信號呢, 這個函數就是獲取這個信號的

pcntl_wifstopped

僅當option選項爲WUNTRACED時有效, 未理解, 不表

pcntl_wtermsig

同上

綜合實例代碼:

$res = pcntl_waitpid($pid, $status, WNOHANG);
//FileLog::log("pid is $pid; wait result is $res");
if($res == -1 || $res > 0){
    if(!pcntl_wifexited($status)){
        //進程非正常退出
        FileLog::log("service stop unusally; pid is $pid");
    }else{
        //獲取進程終端的退出狀態碼;
        $code = pcntl_wexitstatus($status);
        FileLog::log("service stop code: $code;pid is $pid ");
    }

    if(pcntl_wifsignaled($status)){
        //不是經過接受信號中斷
        FileLog::log("service stop not by signal;pid is $pid ");
    }else{
        $signal = pcntl_wtermsig($status);
        FileLog::log("service stop by signal $signal;pid is $pid");
    }
}

上面的這個代碼就經過根據pcntl_waitpid的返回結果和狀態碼對子進程由於不一樣緣由中斷作了不一樣的處理

相關文章
相關標籤/搜索