初步進行PHP多進程編程的介紹

來源:互聯網
上載者:User
這篇文章主要介紹了關於初步進行PHP多進程編程的介紹,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

羨慕火影忍者裡鳴人的影分身嗎?沒錯,PHP程式是可以開動影分身的!想完成任務,又覺得一個進程太慢,那麼,試試用多進程來搞吧。這篇文章將會介紹一下PHP多進程的基本需求,如何建立多進程以及基本的訊號控制,暫時不會告訴你如何進行處理序間通訊和資訊共用。

1. 準備

在動手之前,請確定你用的不是M$ Windows平台(因為我沒有Windows)。Linux / BSD / Unix應該都是沒問題的。確認好了工作環境以後一起來看看我們需要的PHP模組是否都有。開啟終端輸入下面的命令:

$ php -m

這個命令檢查並列印當前PHP所有開啟的擴充,看一下pcntlposix是否在輸出的列表中。

1.1. pcntl

如果找不到pcntl,八成是編譯的時候沒把這個擴充編譯進去。如果你和我一樣是編譯安裝的PHP,那麼需要重新編譯安裝PHP。在配置的時候記得加上--enable-pcntl參數即可。

$ cd /path/to/php_source_code_dir $ ./configure [some other options] --enable-pcntl$ make && make install

1.2. posix

這貨一般預設就會裝上,只要你編譯的時候沒有加上--disable-posix

2. 預備知識

在繼續之前,你還需要對Linux多進程有一點瞭解。多進程是咋回事呢?這裡可跟火影忍者裡的影分身稍微有點不同。首先,鳴人從小長到大,比如16歲,咳。有一天他發動了影分身,分出了5個他。顯然,這些分身也是16歲的鳴人而不是剛出生啥也不懂就會哭的嬰兒(那叫複製)。然後,不一樣的地方來了:分身們變成了獨立的人各自去做各自的事,互相之間不再知道其他分身和原身都做了什麼(當然不會像動畫片裡一樣積累經驗給原身啦)。除非,他們互相之間有交流,不然,只有16歲之前的事情才是他們共同的記憶。

有同學說了,老大你這不坑爹呢嗎?我又沒看過火影忍者!那你去看一遍好了……

最後,預備知識完了,就是大致瞭解一下主進程開出來的子進程是怎麼回事。子進程的代碼和主進程是完全一樣的,還有一部分一樣的東西就是直到發動影分身之前執行的所有內容。具體請參見《作業系統》課程。

3. 影分身之術

所以呢,沒有點基礎知識怎麼能理解捲軸裡的內容呢?開啟捲軸首先看到了一個單詞:fork。

3.1. fork

叉子?叉子是分岔的,一個變多個嘛!差不多就是這個意思。建立子進程就用這個命令。這裡需要用到pcntl_fork()函數。(可以先簡單看一下PHP手冊關於這個函數的介紹。)建立一個PHP指令碼:

$pid = pcntl_fork(); // 一旦調用成功,事情就變得有些不同了if ($pid == -1) {    die('fork failed');} else if ($pid == 0) {} else {}

pcntl_fork()函數建立一個子進程,子進程和父進程唯一的區別就是PID(進程ID)和PPID(父進程ID)不同。在終端下查看進程用ps命令(問問man看ps怎麼用:man ps)。當函數傳回值為-1的時候,說明fork失敗了。試試在if前面加一句:echo $pid . PHP_EOL;。運行你的指令碼,輸出可能像下面這樣(結果說明子進程和父進程的代碼是相同的):

67789 # 這個是父進程列印的0     # 這個是子進程列印的

pcntl_fork()函數調用成功後,在父進程中會返回子進程的PID,而在子進程中返回的是0。所以,下面直接用if分支來控制父進程和子進程做不同的事。

3.2. 分配任務

然後我們來說說鳴人16歲那次影分身的事兒,給原身和分身分配兩個簡單的輸出任務:

$parentPid = getmypid(); // 這就是傳說中16歲之前的記憶$pid = pcntl_fork(); // 一旦調用成功,事情就變得有些不同了if ($pid == -1) {    die('fork failed');} else if ($pid == 0) {    $mypid = getmypid(); // 用getmypid()函數擷取當前進程的PID    echo 'I am child process. My PID is ' . $mypid . ' and my father's PID is ' . $parentPid . PHP_EOL;} else {    echo 'Oh my god! I am a father now! My child's PID is ' . $pid . ' and mine is ' . $parentPid . PHP_EOL;}

輸出的結果可能是這樣:

Oh my god! I am a father now! My child's PID is 68066 and mine is 68065I am child process. My PID is 68066 and my father's PID is 68065

再強調一下,pcntl_fork()調用成功以後,一個程式變成了兩個程式:一個程式得到的$pid變數值是0,它是子進程;另一個程式得到的$pid的值大於0,這個值是子進程的PID,它是父進程。在下面的分支語句中,由於$pid值的不同,運行了不同的代碼。再次強調一下:子進程的代碼和父進程的是一樣的。所以就要通過分支語句給他們分配不同的任務。

3.3. 子進程回收

剛剛有man ps嗎?一般我習慣用ps aux加上grep命令來尋找運行著的後台進程。其中有一列STAT,標識了每個進程的運行狀態。這裡,我們關注狀態Z:殭屍(Zombie)。當子進程比父進程先退出,而父進程沒對其做任何處理的時候,子進程將會變成殭屍進程。Oops,又跟火影裡的影分身不一樣了。鳴人的影分身被乾死了以後就自動消失了,但是這裡的子進程分身死了話還留著一個空殼在,直到父進程回收它。殭屍進程雖然不佔什麼記憶體,但是很礙眼,院子裡一堆躺著的殭屍怎麼都覺得怪怪的。(別忘了它們還佔用著PID)

一般來說,在父進程結束之前回收掛掉的子進程就可以了。在pcntl擴充裡面有一個pcntl_wait()函數,它會將父進程掛起,直到有一個子進程退出為止。如果有一個子進程變成了殭屍的話,它會立即返回。所有的子進程都要回收,所以多等等也沒關係啦!

3.4. 父進程先掛了

如果父進程先掛了怎麼辦?會發生什嗎?什麼也不會發生,子進程依舊還在運行。但是這個時候,子進程會被交給1號進程,1號進程成為了這些子進程的繼父。1號進程會很好地處理這些進程的資源,當它們結束時1號進程會自動回收資源。所以,另一種處理殭屍進程的臨時辦法是關閉它們的父進程。

4. 訊號

一般多進程的事兒講到上面就完了,可是訊號在系統中確實是一個非常重要的東西。訊號就是號誌,點亮一個號誌,程式就會做出反應。這個你一定用過,比如說在終端下運行某個程式,等了半天也沒什麼反應,可能你會按 Ctrl+C 來關閉這個程式。實際上,這裡就是通過鍵盤向程式發送了一個中斷的訊號:SIGINT。有時候進程失去響應了還會執行kill [PID]命令,未加任何其他參數的話,程式會接收到一個SIGTERM訊號。程式收到上面兩個訊號的時候,預設都會結束執行,那麼是否有可能改變這種預設行為呢?必須能啊!

4.1. 註冊訊號

人是活的程式也是活的,只不過程式需要遵循人制定的規則來運行。現在開始給訊號重新設定規則,這裡用到的函數是pcntl_signal()(繼續之前為啥不先查查PHP手冊呢?)。下面這段程式將給SIGINT重新定義行為,注意看好:

// 定義一個處理器,接收到SIGINT訊號後只輸出一行資訊function signalHandler($signal) {    if ($signal == SIGINT) {        echo 'signal received' . PHP_EOL;    }}// 訊號註冊:當接收到SIGINT訊號時,調用signalHandler()函數pcntl_signal(SIGINT, 'signalHandler');while (true) {    sleep(1);    // do something    pcntl_signal_dispatch(); // 接收到訊號時,調用註冊的signalHandler()}

執行一下,隨時按下 Ctrl+C 看看會發生什麼事。

4.2. 訊號分發

說明一下:pcntl_signal()函數僅僅是註冊訊號和它的處理方法,真正接收到訊號並調用其處理方法的是pcntl_signal_dispatch()函數。試試把// do something替換成下面這段代碼:

for ($i = 0; $i < 1000000; $i++) {    echo $i . PHP_EOL;    usleep(100000);}

在終端下執行這個指令碼,當它不停輸出數位時候嘗試按下 Ctrl+C 。看看程式有什麼響應?嗯……什麼都沒有,除了螢幕可能多了個^C以外,程式一直在不停地輸出數字。因為程式一直沒有執行到pcntl_signal_dispatch(),所以就並沒有調用signalHandler(),所以就沒有輸出signal received

4.3. 版本問題

如果認真看了PHP文檔,會發現pcntl_signal_dispatch()這個函數是PHP 5.3以上才支援的,如果你的PHP版本大於5.3,建議使用這個方法調用訊號處理器。5.3以下的版本需要在註冊訊號之前加一句:declare(ticks = 1);表示每執行一條低級指令,就檢查一次訊號,如果檢測到註冊的訊號,就調用其訊號處理器。想想就挺不爽的,幹嘛一直都檢查?還是在我們指定的地方檢查一下就好。

4.4. 感受殭屍進程

現在我們回到子進程回收的問題上(差點忘了= =")。當你的一個子進程掛了(或者說是結束了),但是父進程還在運行中並且可能很長一段時間不會退出。一個殭屍進程從此站起來了!這時,保護傘公司(核心)發現它的地盤裡出現了一個殭屍,這個殭屍是誰兒子呢?看一下PPID就知道了。然後,核心給PPID這個進程(也就是殭屍進程的父進程)發送一個訊號:SIGCHLD。然後,你知道怎麼在父進程中回收這個子進程了嗎?提示一下,用pcntl_wait()函數。

4.5. 發送訊號

希望剛剛有認真man過kill命令。它其實就是向進程發送訊號,在PHP中也可以調用posix_kill()函數來達到相同的效果。有了它就可以在父進程中控制其他子進程的運行了。比如在父進程結束之前關閉所有子進程,那麼fork的時候在父進程記錄所有子進程的PID,父進程結束之前依次給子進程發送結束訊號即可。

5. 實踐

PHP的多進程跟C還是挺像的,搞明白了以後用其他語言寫的話也大同小異差不多都是這麼個情況。如果有空的話,嘗試寫一個小程式,切身體會一下箇中滋味:

  1. 16歲的鳴人發送影分身,分出5個分身

  2. 每個分身隨機生存10到30秒,每秒都輸出點什麼

  3. 保證原身能感受到分身的結束,然後開動另一個分身,保證最多有5個分身

  4. 不使用nohup,讓原身在終端關閉後依舊能夠運行

  5. 把分身數量(5)寫進一個設定檔裡,當給原身發送訊號(可以考慮用SIGUSR1或SIGUSR2)時,原身讀取設定檔並更新允許的分身最大數量

  6. 如果分身多了,關閉幾個;如果少了,再分出來幾個

提示:

  1. while迴圈保證進程運行,注意sleep以免100%的CPU佔用

  2. 運行進程的終端被關閉時,程式會收到一個SIGHUP訊號

  3. 可以用parse_ini_file()函數解析INI設定檔

以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.