使用PHP真正的多進程運行模式,適用於數據採集、郵件羣發、數據源更新、tcp服務器等環節。php
PHP有一組進程控制函數(編譯時須要 –enable-pcntl與posix擴展),使得php能在*nix系統中實現跟c同樣的建立子進程、使用exec函數執行程序、處理信號等功能。 PCNTL使用ticks來做爲信號處理機制(signal handle callback mechanism),能夠最小程度地下降處理異步事件時的負載。何謂ticks?Tick 是一個在代碼段中解釋器每執行 N 條低級語句就會發生的事件,這個代碼段須要經過declare來指定。linux
經常使用的PCNTL函數
1. pcntl_alarm ( int $seconds )
設置一個$seconds秒後發送SIGALRM信號的計數器數據庫
2. pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] )
爲$signo設置一個處理該信號的回調函數。下面是一個隔5秒發送一個SIGALRM信號,並由signal_handler函數獲取,而後打印一個「Caught SIGALRM」的例子:編程
1數組 2服務器 3數據結構 4多線程 5併發 6異步 7 8 9 10 11 12 13 14 15 |
|
3. pcntl_exec ( string $path [, array $args [, array $envs ]] )
在當前的進程空間中執行指定程序,相似於c中的exec族函數。所謂當前空間,即載入指定程序的代碼覆蓋掉當前進程的空間,執行完該程序進程即結束。
1 2 3 4 5 6 7 8 9 10 11 |
|
4. pcntl_fork ( void )
爲當前進程建立一個子進程,而且先運行父進程,返回的是子進程的PID,確定大於零。在父進程的代碼中能夠用 pcntl_wait(&$status)暫停父進程知道他的子進程有返回值。注意:父進程的阻塞同時會阻塞子進程。可是父進程的結束不影響子進程的運行。
父進程運行完了會接着運行子進程,這時子進程會從執行pcntl_fork()的那條語句開始執行(包括此函數),可是此時它返回的是零(表明這是一個子進程)。在子進程的代碼塊中最好有exit語句,即執行完子進程後當即就結束。不然它會又重頭開始執行這個腳本的某些部分。
注意兩點:
1. 子進程最好有一個exit;語句,防止沒必要要的出錯;
2. pcntl_fork間最好不要有其它語句,例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
5. pcntl_wait ( int &$status [, int $options ] )
阻塞當前進程,只到當前進程的一個子進程退出或者收到一個結束當前進程的信號。使用$status返回子進程的狀態碼,並能夠指定第二個參數來講明是否以阻塞狀態調用:
1. 阻塞方式調用的,函數返回值爲子進程的pid,若是沒有子進程返回值爲-1;
2. 非阻塞方式調用,函數還能夠在有子進程在運行但沒有結束的子進程時返回0。
6. pcntl_waitpid ( int $pid , int &$status [, int $options ] )
功能同pcntl_wait,區別爲waitpid爲等待指定pid的子進程。當pid爲-1時pcntl_waitpid與pcntl_wait 同樣。在pcntl_wait和pcntl_waitpid兩個函數中的$status中存了子進程的狀態信息,這個參數能夠用於 pcntl_wifexited、pcntl_wifstopped、pcntl_wifsignaled、pcntl_wexitstatus、 pcntl_wtermsig、pcntl_wstopsig、pcntl_waitpid這些函數。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
子進程在輸出child process等字樣以後sleep了2秒才結束,而父進程阻塞着直到子進程退出以後才繼續運行。
7. pcntl_getpriority ([ int $pid [, int $process_identifier ]] )
取得進程的優先級,即nice值,默認爲0,在個人測試環境的linux中(CentOS release 5.2 (Final)),優先級爲-20到19,-20爲優先級最高,19爲最低。(手冊中爲-20到20)。
8. pcntl_setpriority ( int $priority [, int $pid [, int $process_identifier ]] )
設置進程的優先級。
9. posix_kill
能夠給進程發送信號
10. pcntl_singal
用來設置信號的回調函數
當父進程退出時,子進程如何得知父進程的退出
當父進程退出時,子進程通常能夠經過下面這兩個比較簡單的方法得知父進程已經退出這個消息:
1. 當父進程退出時,會有一個INIT進程來領養這個子進程。這個INIT進程的進程號爲1,因此子進程能夠經過使用getppid()來取得當前父進程的pid。若是返回的是1,代表父進程已經變爲INIT進程,則原進程已經推出。
2. 使用kill函數,向原有的父進程發送空信號(kill(pid, 0))。使用這個方法對某個進程的存在性進行檢查,而不會真的發送信號。因此,若是這個函數返回-1表示父進程已經退出。
除了上面的這兩個方法外,還有一些實現上比較複雜的方法,好比創建管道或socket來進行時時的監控等等。
PHP多進程採集數據的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
|
php在pcntl_fork()後生成的子進程(一般爲殭屍進程)必須由pcntl_waitpid()函數進行資源釋放。但在 pcntl_waitpid()不必定釋放的就是當前運行的進程,也多是過去生成的殭屍進程(沒有釋放);也多是併發時其它訪問者的殭屍進程。但可使用posix_kill($cid, SIGTERM)在子進程結束時殺掉它。
子進程會自動複製父進程空間裏的變量。
PHP多進程編程示例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
若是不須要阻塞進程,而又想獲得子進程的退出狀態,則能夠註釋掉pcntl_wait($status)語句,或寫成:
1 2 3 4 5 |
|
在上面的代碼中,若是父進程退出(使用exit函數退出或redirect),則會致使子進程成爲殭屍進程(會交給init進程控制),子進程再也不執行。
殭屍進程是指的父進程已經退出,而該進程dead以後沒有進程接受,就成爲殭屍進程.(zombie)進程。任何進程在退出前(使用exit退出) 都會變成殭屍進程(用於保存進程的狀態等信息),而後由init進程接管。若是不及時回收殭屍進程,那麼它在系統中就會佔用一個進程表項,若是這種殭屍進程過多,最後系統就沒有能夠用的進程表項,因而也沒法再運行其它的程序。
預防殭屍進程有如下幾種方法:
1. 父進程經過wait和waitpid等函數使其等待子進程結束,而後再執行父進程中的代碼,這會致使父進程掛起。上面的代碼就是使用這種方式實現的,但在WEB環境下,它不適合子進程須要長時間運行的狀況(會致使超時)。
使用wait和waitpid方法使父進程自動回收其殭屍子進程(根據子進程的返回狀態),waitpid用於臨控指定子進程,wait是對於全部子進程而言。
2. 若是父進程很忙,那麼能夠用signal函數爲SIGCHLD安裝handler,由於子進程結束後,父進程會收到該信號,能夠在handler中調用wait回收
3. 若是父進程不關心子進程何時結束,那麼能夠用signal(SIGCHLD, SIG_IGN)通知內核,本身對子進程的結束不感興趣,那麼子進程結束後,內核會回收,並再也不給父進程發送信號,例如:
1 2 3 4 5 |
|
4. 還有一個技巧,就是fork兩次,父進程fork一個子進程,而後繼續工做,子進程再fork一個孫進程後退出,那麼孫進程被init接管,孫進程結束後,init會回收。不過子進程的回收還要本身作。下面是一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
在fork()/execve()過程當中,假設子進程結束時父進程仍存在,而父進程fork()以前既沒安裝SIGCHLD信號處理函數調用 waitpid()等待子進程結束,又沒有顯式忽略該信號,則子進程成爲殭屍進程,沒法正常結束,此時即便是root身份kill-9也不能殺死殭屍進程。補救辦法是殺死殭屍進程的父進程(殭屍進程的父進程必然存在),殭屍進程成爲」孤兒進程」,過繼給1號進程init,init會按期調用wait回收清理這些父進程已退出的殭屍子進程。
因此,上面的示例能夠改爲:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
怎樣產生殭屍進程的
一個進程在調用exit命令結束本身的生命的時候,其實它並無真正的被銷燬,而是留下一個稱爲殭屍進程(Zombie)的數據結構(系統調用exit,它的做用是使進程退出,但也僅僅限於將一個正常的進程變成一個殭屍進程,並不能將其徹底銷燬)。在Linux進程的狀態中,殭屍進程是很是特殊的一種,它已經放棄了幾乎全部內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其餘進程收集,除此以外,殭屍進程再也不佔有任何內存空間。它須要它的父進程來爲它收屍,若是他的父進程沒安裝SIGCHLD信號處理函數調用wait或waitpid()等待子進程結束,又沒有顯式忽略該信號,那麼它就一直保持殭屍狀態,若是這時父進程結束了,那麼init進程自動會接手這個子進程,爲它收屍,它仍是能被清除的。可是若是若是父進程是一個循環,不會結束,那麼子進程就會一直保持殭屍狀態,這就是爲何系統中有時會有不少的殭屍進程。
任何一個子進程(init除外)在exit()以後,並不是立刻就消失掉,而是留下一個稱爲殭屍進程(Zombie)的數據結構,等待父進程處理。這是每一個子進程在結束時都要通過的階段。若是子進程在exit()以後,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是」Z」。若是父進程能及時 處理,可能用ps命令就來不及看到子進程的殭屍狀態,但這並不等於子進程不通過殭屍狀態。
若是父進程在子進程結束以前退出,則子進程將由init接管。init將會以父進程的身份對殭屍狀態的子進程進行處理。
另外,還能夠寫一個php文件,而後在之後臺形式來運行它,例如:
1 2 3 4 5 6 7 8 9 |
|
而後在insertLargeData.php文件中作數據庫操做。也能夠用cronjob + php的方式實現大數據量的處理。
若是是在終端運行php命令,當終端關閉後,剛剛執行的命令也會被強制關閉,若是你想讓其不受終端關閉的影響,可使用nohup命令實現:
1 2 3 4 5 6 7 8 9 |
|
你還可使用screen命令代替nohup命令。