PHP 利用 ignore_user_abort + pcntl_fork 實作背景執行 (來來哥範例)


php5.4-300x300傳統的 PHP 執行方式

傳統 PHP 執行 Script 都是在同一個 Thread,當我們利用 PHP 設計網頁或者 WebService 時,難免會遇到一些不需要當下回傳執行結果的案例,像是轉檔等背景執行之類的工作。由於這些工作可能會耗掉比較長的時間來運算,總不能為了要執行就讓使用者一直等下去。

利用 ignore_user_abort 忽略連線中斷

從上面的問題開始討論,假設有個轉檔的 PHP Script,執行太久造成連線中斷 (Timeout),一般來說 HTTP Server (通常是 Apache) 就會銷燬這個正在執行的 PHP Process!這下遭了,程式只跑了一半就 GG,圖轉了一半還好,如果是錢轉了一半可就...

為了解決這樣的問題,PHP 有個 ignore_user_abort(true); 函式可以忽略連線中斷而繼續執行,我們通常在使用上還會搭配 set_time_limit(0); 來關閉 PHP 的執行限制時間。這樣一來,無論使用者是否關閉瀏覽器,我們的程式皆可以在 Server 繼續執行。以下是「來來哥」模擬程式 PHP 範例 (沒聽過來來哥請自行 Youtube),程式會將來來哥說的話寫到 say.txt 檔案中,PHP Code 如下:

執行過程我們可以用 terminal 看一下執行結果 (Linux 使用 tail -f say.txt 即時顯示檔案內容),如下:

ccb-console

執行過程我們可以看到瀏覽器一直處於連線狀態!接下來我來利用 pcntl_fork 解決這個問題。

加上 pcntl_fork() 建立子執行緒

有了 ignore_user_abort(true); 只是表示 PHP 不會因為連線而被中斷執行,但是執行過程中這個 HTTP Request 仍然沒有結束,使用者的網頁就會一直處於等待回應的狀態。當然也可以用些奇怪的方法解決 (Dirty Solution),像是用 AJAX 呼叫、或者將 POST 導向一個隱藏的 iFrame 等等作法。但是遇到了 WebService 這樣鳥招可就不太優了,所以接下來介紹利用 PHP-pcntl Lib 中的 pcntl_fork() 命令將工作放在子執行緒執行,避免主執行緒等待執行結果。

典型的 pcntl_fork Sample Code Pattern 如下:

 然後我們將原本要執行的工作放到 fork thread 中來執行,程式範例如下:

執行的過程中我們可以看到瀏覽器馬上就結束連線,但是程式會繼續在 Server 背景執行,我們也可以多重新整理幾次,這樣就會有很多「來來哥」瘋狂的「來來來!」。

CentOS PHP 官方套件加裝 pcntl library 外掛

由於 CentOS 內建的 PHP 並沒有 pcntl 函式庫,在不重新編譯 PHP 的方式下可以透過 PHP extension 載入額外的函式。首先利用 phpinfo(); 查詢與確認 PHP 版本 (php -v 也行),然後到 http://www.php.net/releases/ 下載 Source Code 進行編譯,請透過以下方法僅編譯 pcntl extension 即可。

討論

雖然 ignore_user_abort(true); 好用,但是我們一但搭配了 set_time_limit(0); 來使用就表示這個 PHP 執行時間是沒有限制的,若是程式寫的不好或者執行佔用太多資源,很有可能會癱瘓系統運作,在設計時必須要謹慎使用!避免過多的執行緒癱瘓系統。此外 pcntl_fork 目前僅支援 Linux 環境,如果您是用 Windows 執行 PHP 可能就先抱歉了!

參考資料

Facebook 留言

廣告

樂樂童鞋