孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那么那些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,並由init進程對它們完成狀態收集工作。
僵屍進程:一個進程使用fork創建子進程,如果子進程退出,而父進程並沒有調用wait或waitpid獲取子進程的狀態信息,那么子進程的進程描述符仍然保存在系統中。這種進程稱之為僵死進程。
僵屍進程危害:如果進程不調用wait / waitpid的話, 那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統所能使用的進程號是有限的,如果大量的產生僵死進程,將因為沒有可用的進程號而導致系統不能產生新的進程. 此即為僵屍進程的危害,應當避免。任何一個子進程(init除外)在exit()之后,並非馬上就消失掉,而是留下一個稱為僵屍進程(Zombie)的數據結構,等待父進程處理。
已經產生的僵屍進程,解決方法:kill掉父進程,它產生的僵死進程就變成了孤兒進 程,這些孤兒進程會被init進程接管,init進程會wait()這些孤兒進程,釋放它們占用的系統進程表中的資源。
僵屍進程解決辦法
(1)通過信號機制
子進程退出時向父進程發送SIGCHILD信號,父進程處理SIGCHILD信號。在信號處理函數中調用wait進行處理僵屍進程。
(2)fork兩次
《Unix 環境高級編程》8.6節說的非常詳細。原理是將子進程成為孤兒進程,從而其的父進程變為init進程,通過init進程可以處理僵屍進程。
多進程與多線程比較
對比維度 |
多進程 |
多線程 |
總結 |
數據共享、同步 |
數據共享復雜,需要用IPC;數據是分開的,同步簡單 |
因為共享進程數據,數據共享簡單,但也是因為這個原因導致同步復雜 |
各有優勢 |
內存、CPU |
占用內存多,切換復雜,CPU利用率低 |
占用內存少,切換簡單,CPU利用率高 |
線程占優 |
創建銷毀、切換 |
創建銷毀、切換復雜,速度慢 |
創建銷毀、切換簡單,速度很快 |
線程占優 |
編程、調試 |
編程簡單,調試簡單 |
編程復雜,調試復雜 |
進程占優 |
可靠性 |
進程間不會互相影響 |
一個線程掛掉將導致整個進程掛掉 |
進程占優 |
分布式 |
適應於多核、多機分布式;如果一台機器不夠,擴展到多台機器比較簡單 |
適應於多核分布式 |
進程占優
|
1)需要頻繁創建銷毀的優先用線程
原因請看上面的對比。
這種原則最常見的應用就是Web服務器了,來一個連接建立一個線程,斷了就銷毀線程,要是用進程,創建和銷毀的代價是很難承受的
2)需要進行大量計算的優先使用線程
所謂大量計算,當然就是要耗費很多CPU,切換頻繁了,這種情況下線程是最合適的。
這種原則最常見的是圖像處理、算法處理。
3)強相關的處理用線程,弱相關的處理用進程
什么叫強相關、弱相關?理論上很難定義,給個簡單的例子就明白了。
一般的Server需要完成如下任務:消息收發、消息處理。“消息收發”和“消息處理”就是弱相關的任務,而“消息處理”里面可能又分為“消息解碼”、“業務處理”,這兩個任務相對來說相關性就要強多了。因此“消息收發”和“消息處理”可以分進程設計,“消息解碼”、“業務處理”可以分線程設計。
當然這種划分方式不是一成不變的,也可以根據實際情況進行調整。
4)可能要擴展到多機分布的用進程,多核分布的用線程
原因請看上面對比。
5)都滿足需求的情況下,用你最熟悉、最拿手的方式
至於“數據共享、同步”、“編程、調試”、“可靠性”這幾個維度的所謂的“復雜、簡單”應該怎么取舍,我只能說:沒有明確的選擇方法。但我可以告訴你一個選擇原則:如果多進程和多線程都能夠滿足要求,那么選擇你最熟悉、最拿手的那個。
需要提醒的是:雖然我給了這么多的選擇原則,但實際應用中基本上都是“進程+線程”的結合方式,千萬不要真的陷入一種非此即彼的誤區。
消耗資源:
從內核的觀點看,進程的目的就是擔當分配系統資源(CPU時間、內存等)的基本單位。線程是進程的一個執行流,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。
線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個數據可能會有較大的區別。
通訊方式:
進程之間傳遞數據只能是通過通訊的方式,即費時又不方便。線程時間數據大部分共享(線程函數內部不共享),快捷方便。但是數據同步需要鎖對於static變量尤其注意
線程自身優勢:
提高應用程序響應;使多CPU系統更加有效。操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上;
改善程序結構。一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利於理解和修改。
1. [代碼]PHP實現多進程並行操作(可做守護進程)
1 /** 2 * 入口函數 3 * 將此文件保存為 ProcessOpera.php 4 * 在terminal中運行 /usr/local/php/bin/php ProcessOpera.php & 5 * 查看進程 ps aux|grep php 6 */ 7 8 9 ProcessOpera("runCode", array(), 8); 10 11 /** 12 * run Code 13 */ 14 function runCode($opt = array()) { 15 //需要在守護進程中運行的代碼 16 } 17 18 /** 19 * $func為子進程執行具體事物的函數名稱 20 * $opt為$func的參數 數組形式 21 * $pNum 為fork的子進程數量 22 */ 23 function ProcessOpera($func, $opts = array(), $pNum = 1) { 24 while(true) { 25 $pid = pcntl_fork(); 26 if($pid == -1) { 27 exit("pid fork error"); 28 } 29 if($pid) { 30 static $execute = 0; 31 $execute++; 32 if($execute >= $pNum) { 33 pcntl_wait($status); 34 $execute--; 35 } 36 } else { 37 while(true) { 38 //somecode 39 $func($opts); 40 sleep(1); 41 } 42 exit(0); 43 } 44 } 45 }
2. [代碼]PHP實現多線程操作
1 class My extends Thread { 2 protected $name; 3 public $runing; 4 function __construct($name){ 5 $this->runing=1; 6 $this->param=0; 7 $this->name=$name; 8 } 9 public function run() { 10 while($this->runing){ 11 if($this->param){ 12 $time=rand(1,5); 13 echo 'I am thread '.$this->name.',pid: '.$this->getCreatorId().",param: {$this->param},need {$time}s\n"; 14 sleep($time); 15 $this->param=0; 16 }else{ 17 echo "Thread {$this->name} waiting...\n"; 18 } 19 sleep(1); 20 } 21 } 22 } 23 $pool=array(); 24 $pool[]=new My('a'); 25 $pool[]=new My('b'); 26 $pool[]=new My('c'); 27 //開啟所有線程 28 foreach ($pool as $w) { 29 $w->start(); 30 } 31 //派發任務 32 unset($w); 33 for($i=1;$i<10;$i++){ 34 $woker_content=$i; 35 while(1){ 36 foreach($pool as $w){ 37 if(!$w->param){ 38 $w->param=$woker_content; 39 echo "Thread {$w->name} empty,put param {$woker_content}.\n"; 40 break 2; 41 } 42 } 43 sleep(1); 44 } 45 } 46 47 unset($w); 48 while(count($pool)){ 49 foreach ($pool as $k => $w) { 50 if(!$w->param){ 51 $w->runing=false; 52 unset($pool[$k]); 53 echo "Thread {$w->name} end,exit!\n"; 54 } 55 } 56 sleep(1); 57 } 58 59 echo 'All thread end!';