PHP作為解釋器運行通過線程或者進程都能實現(如果使用Apache,那么就可能使用多線程模型。使用php-fpm,就是使用多進程模型,這里以多進程模型解釋)。服務器每接收到一個請求就要起一個PHP進程,平均一個PHP進程消耗內存2M左右(默認最大為8M,參數可以設置)。獨立的進程讓PHP能專一的做自己的解釋工作,程序員也從復雜的代碼邏輯中走出來,不用擔心資源的競爭和各種鎖問題。獨立進程雖好但這也導致想通過多進程或者異步來提速成本非常的高(主要是開發難度)。如果一定要通過PHP實現多進程和異步其實是很容易做到的。
PHP有很多第三方擴展,比如Swoole能讓PHP像Node一樣實現異步。PHP官方擴展庫pcntl_*能很簡單的實現多進程。擴展雖好,但實際應用時切忌要慎重,便利的同時風險也來了。比如對多進程的控制,處理不好很容易導致程序死鎖,CPU內存爆表、服務器宕機。異步回調的Coding方式與PHP本身的編程思想有一定出入,駕馭不好也是災難。
當然也不能說的太嚇人,在實際的項目中我們有很多場景不得不考慮通過多進程或者異步來優化程序。這里舉一個很常見的例子『發送消息通知』,比如短信和郵件。這里說一個實際的場景:企業需要給200W用戶發短信通知,短信接口支持最大100次/秒的調用頻率,短信接口每次調用耗時300毫秒。如果單進程跑腳本的話,需要7天才能把短信發完。如果我們起30個進程,每秒能發送100條短信,6個小時內能發完,能提速30倍。優化方案確定之后,我們再看如何通過PHP去實現這樣一個腳本。
//通過pcntl擴展創建多進程,參見如下代碼; function demo(array $phoneList){ $cnt = count($phoneList); //測試數組大小 $slice = 30; //需要調用的進程數量 $master = array_chunk($phoneList,floor($cnt/$slice)); $childList = []; while($slice >= 0) { $pid = pcntl_fork(); if($pid > 0){ $childList[$pid] = 1; //$pid>0表示當前還在執行父進程的代碼 //這里最好啥都不做,每次執行pcntl_fork都會執行這里的代碼。 //這里的代碼執行完之后 會將$pid設置為0,然后jump到pcntl_fork代碼之后,重新做判斷; }elseif($pid == 0){ //這里寫我們的邏輯 foreach($master[$slice] as $val) { //這里發生短信 echo sprintf("%s Child:%s \r\n",$slice,$val); } //子進程執行完之后務必需要關閉; exit(); }else { //程序發生錯誤也需要關閉程序 exit(); } $slice--; } // 等待所有子進程結束后回收資源 while(!empty($childList)){ $childPid = pcntl_wait($status); if ($childPid > 0){ unset($childList[$childPid]); } } } /** 運行的結果如下,phone不是連續的 Slice id:19,phone:66558 Slice id:23,phone:79921 Slice id:19,phone:66559 Slice id:23,phone:79922 Slice id:19,phone:66560 Slice id:23,phone:79923 Slice id:19,phone:66561 Slice id:23,phone:79924 Slice id:19,phone:66562 Slice id:23,phone:79925 **/
通過pcntl擴展,幾句代碼就使用多進程將發消息通知的功能提速了30倍。不過這么簡單的多進程編碼,我為什么會在文章開始形容的如此復雜呢?
重點和難點還是進程間通信,因為我們給用戶發短信的每個子進程是相對獨立的,進程之間沒有通信,不會互相傳遞數據狀態。所以不會發生資源搶占與鎖問題。假如需求發生變化,我們需要按用戶的活躍度高低給用戶發短信,該怎么做?
通俗點解釋如下:一個盤子里有30個蘋果,需要發給30個人,由3個人負責發蘋果。最簡單的辦法就是我們先把蘋果分成3份,3個人一人一份,很快就能發完。但是如果我們要按照蘋果的大小順序去發,把大蘋果先發出去,此時我們就沒辦法分成3份了,只能三個人互相去掙當前最大的,很容易就打起來。那該怎么做呢?最常見的辦法就是使用一個工具把所有蘋果按由大到下的順序放在里面,每次只能取一個,這樣就解決了資源搶占的問題。
關於進程間資源搶占的問題非常的復雜,編碼難度非常高,這也是為什么很少使用PHP跑多進程的原因。當需要用到多進程時我們更願意去使用Python或者Java,它們對多線程封裝的更好。需要重點說的是PHP並不是不能寫多進程的程序,也不是像其他人說的不穩定,而是編碼費時,維護成本高。