php多進程實例


  在前面的文章《php多進程和多線程的比較》中已經介紹了一些多進程的基礎知識,這篇文章呢,主要是結合實例學習一下,php多進程的用途。文章分為三部分,第一部分介紹多進程用到的一些函數;第二部分介紹一個簡單的多進程示例,第三部分介紹一個利用php多進程的用途——守護進程。

 多進程相關函數

  1.$pid = pcntl_fork();//創建一個子進程

   pcntl_fork 的返回值是一個int值 。如果$pid=-1 ,fork進程失敗 ;如果$pid=0, 當前的上下文環境為worker ; 如果$pid>0 當前的上下文環境為master,這個pid就是fork的worker的pid。

  2.$pid = pcntl_wait($status, WNOHANG)// 等待或返回fork的子進程狀態

    pcntl_wait()將會存儲狀態信息到status 參數上;WNOHANG表示如果沒有子進程退出立刻返回。函數的返回值$pid是一個Int值,如果是子進程退出了,表示子進程號;如果發生錯誤返回-1,如果提供了 WNOHANG作為option,並且沒有可用子進程時返回0。主要作用是防止產生僵屍進程。子進程結束時,父進程沒有等待它(通過調用wait或者waitpid),那么子進程結束后不會釋放所有資源,這就是僵屍進程。
  3.posix_getpid();//返回當前進程的pid
 
  4.posix_getppid();//返回當前進程父進程的pid
 
  5.bool posix_mkfifo ( string $pathname , int $mode )//創建一個管道(用於子進程之間的通訊,可以想象成文件,但是管道的讀寫都是自動上鎖的,原子性操作)
  $pathname-管道存放的路徑;$mode-即用戶對於該管道的權限,和文件權限umask一樣。返回值bool,true代表創建成功。
  6. fopen ( string $filename , string $mode)//打開管道
  這個就是打開文件的操作,參數和操作文件是一致的。
 
  7.fwrite,fclose,fread//寫,關閉,讀管道,方法和操作文件基本一致
 

 多進程示例

  多進程的一個典型應用就是可以並行的去處理幾件事情,比如並行的去請求幾個url,並行的去發送郵件等。繼續衍生,可以多個進程去消息隊列里面去取任務來執行,也可以模擬Web服務器處理http請求的操作等。下面是一個簡單的示例:

 1 <?php
 2     define('NUM',5);
 3     
 4     //先創建管道,1.pipe是管道名
 5     if(!posix_mkfifo ( "pipefff" , 0666 )){
 6         die("create 1.pipe error");
 7     }
 8     
 9     for($i=0;$i<NUM;$i++){
10         $pid = pcntl_fork();
11         if($pid == -1){
12             die("fork error");
13         }else if($pid == 0){//子進程空間
14             sleep(1);
15             echo "子進程ID:".posix_getpid().PHP_EOL;
16             exit(0);
17         }else{//主進程空間
18             echo "父進程ID:".posix_getpid().PHP_EOL;
19             pcntl_wait($status);
20         }
21     }    
22 
23 unlink("pipefff"); // 刪除管道,已經沒有作用了    
View Code

示例,簡單演示了多進程的創建和管道的創建,以及何為子進程空間。

  一個進一步的例子,用來多進程請求url,可以參考下面的代碼,相比單進程去請求,極大的減少了程序的運行時間(也是因為請求url是I/O密集型的任務,如果是cpu密集型的任務在單核下效果不明顯):

 1 <?php
 2 class WebServer
 3 {
 4     private $list;
 5     public function __construct()
 6     {
 7         $this->list = [];
 8     }
 9     public function worker($request){
10         $pid = pcntl_fork();
11         if($pid == -1){
12             return false;
13         }
14         if($pid > 0){
15             return $pid;
16         }
17         if($pid == 0){
18             $time = $request[0];
19             $method = $request[1];
20             $start = microtime(true);
21             echo getmypid()."\t start ".$method."\tat".$start.PHP_EOL;
22             //sleep($time);
23             $c = file_get_contents($method);
24            // echo getmypid() ."\n";
25             $end = microtime(true);
26             $cost = $end-$start;
27             echo getmypid()."\t stop \t".$method."\tat:".$end."\tcost:".$cost.PHP_EOL;
28             exit(0);
29         }
30     }
31     public function master($requests){
32         $start = microtime(true);
33         echo "All request handle start at ".$start.PHP_EOL;
34         foreach ($requests as $request){
35             $pid = $this->worker($request);
36             if(!$pid){
37                 echo 'handle fail!'.PHP_EOL;
38                 return;
39             }
40             array_push($this->list,$pid);
41         }
42         while(count($this->list)>0){
43             foreach ($this->list as $k=>$pid){
44                 $res = pcntl_waitpid($pid,$status,WNOHANG);
45                 if($res == -1 || $res > 0){
46                     unset($this->list[$k]);
47                 }
48             }
49             usleep(100);
50         }
51         $end = microtime(true);
52         $cost = $end - $start;
53         echo "All request handle stop at ".$end."\t cost:".$cost.PHP_EOL;
54     }
55 }
56 
57 $requests = [
58   [1,'http://www.sina.com'],
59   [2,'http://www.sina.com'],
60   [3,'http://www.sina.com'],
61   [4,'http://www.sina.com'],
62   [5,'http://www.sina.com'],
63   [6,'http://www.sina.com']
64 ];
65 
66 echo "多進程測試:".PHP_EOL;
67 $server = new WebServer();
68 $server->master($requests);
69 
70 echo PHP_EOL."單進程測試:".PHP_EOL;
71 $start = microtime(true);
72 for($i=0;$i<6;$i++){
73     $c = file_get_contents("http://www.sina.com");
74 }
75 $end = microtime(true);
76 $cost = $end - $start;
77 echo "All request handle stop at ".$end."\t cost:".$cost.PHP_EOL;
View Code

 

 多進程的用途

  介紹一個多進程的用途,編寫守護進程。

守護進程是脫離於終端並且在后台運行的進程。守護進程脫離於終端是為了避免進程在執行過程中的信息在任何終端上顯示並且進程也不會被任何終端所產生的終端信息所打斷。

  為什么開發守護進程?
  很多程序以服務形式存在,他沒有終端或UI交互,它可能采用其他方式與其他程序交互,如TCP/UDP Socket, UNIX Socket, fifo。程序一旦啟動便進入后台,直到滿足條件他便開始處理任務。
  創建守護進程步驟:
  1)fork一個子進程;
  2)父進程退出(當前子進程會成為init進程的子進程);
  3)子進程調用setsid(),開啟一個新會話,成為新的會話組長,並且釋放於終端的關聯關系;
  4)子進程再fork子進程,父進程退出(可以防止會話組長重新申請打開終端);
  5)關閉打開的文件描述符;
  6)改變當前工作目錄,由於子進程會繼承父進程的工作目錄,修改工作目錄以釋放對父進程工作目錄的占用。  
  7)清除進程umask。
  參考代碼:
 1 <?php
 2     $pid = pcntl_fork();//創建子進程
 3     if($pid < 0){
 4         die("fork fail");
 5     }else if($pid > 0){
 6         exit();//父進程退出
 7     }
 8     //
 9     if(posix_setsid() === -1){
10         die("Could not detach");//獲取會話控制權
11     }
12 
13     $pid = pcntl_fork();//繼續創建子進程
14     if($pid < 0){
15         die("fork fail");
16     }else if($pid > 0){
17         exit();//父進程退出
18     }
19     echo "可以看到輸出".PHP_EOL;
20     
21     //關閉文件描述符
22     @fclose(STDOUT);//關閉標准輸出
23     @fclose(STDERR);//關閉標准出錯
24     $STDOUT = fopen('/dev/null', "a");
25     $STDERR = fopen('/dev/null', "a");    
26     chdir('/');//改變當前工作目錄
27     umask(0);//清除進程權限
28     echo "不可以看到輸出";
29 ?>    
View Code

 

  下面再說明一個例子,利用php守護進程記錄服務器的cpu使用率,內存使用率信息。采用面向對象的方式來編寫。

  1 <?php
  2 
  3 /*
  4  * 利用php守護進程記錄服務器的cpu使用率,內存使用率信息
  5  */
  6 
  7 class Daemon {
  8 
  9     private $_pidFile;
 10     private $_infoDir;
 11     private $_logFile = null;
 12     private $_jobs = array();
 13 
 14     /*
 15      * 構造函數
 16      * $dir  儲存log和pid的絕對路徑
 17      */
 18 
 19     public function __construct($dir = "/tmp",$openLog = false) {
 20         $this->_checkPcntl();
 21         $this->_setInfoDir($dir);
 22         $this->_pidFile = rtrim($this->_infoDir, "/") . "/" . __CLASS__ . "_pid.log";
 23         if($openLog == true){
 24             $this->_logFile = rtrim($this->_infoDir, "/") . "/" . __CLASS__ . "_pid_log.log"; 
 25             file_put_contents($this->_logFile, "",FILE_APPEND);
 26         }
 27     }
 28 
 29     private function _checkPcntl() {//檢查是否開啟pcntl模塊
 30         !function_exists('pcntl_signal') && die('Error:Need PHP Pcntl extension!');
 31     }
 32 
 33     private function _setInfoDir($dir = null) {//設置信息儲存的目錄
 34         if (is_dir($dir)) {
 35             $this->_infoDir = $dir;
 36         } else {
 37             $this->_infoDir = __DIR__;
 38         }
 39     }
 40 
 41     private function _getPid() {//獲取pid
 42         if (!file_exists($this->_pidFile)) {
 43             return 0;
 44         }
 45         $pid = intval(file_get_contents($this->_pidFile));
 46         if (posix_kill($pid, SIG_DFL)) {//給進程發一個信號
 47             return $pid;
 48         } else {
 49             unlink($this->_pidFile);
 50             return 0;
 51         }
 52     }
 53 
 54     private function _message($message) {//輸出相關信息
 55         printf("%s %d %d %s" . PHP_EOL, date("Y-m-d H:i:s"), posix_getppid(), posix_getpid(), $message);
 56         if ($this->_logFile != null) {
 57             file_put_contents($this->_logFile, date("Y-m-d H:i:s") . " " . posix_getppid() . " " . posix_getpid() . " " . $message . PHP_EOL, FILE_APPEND);
 58         }
 59     }
 60 
 61     private function daemonize() {//創建守護程序
 62         if (!preg_match("/cli/i", php_sapi_name())) {
 63             $this->_message("'Should run in CLI");
 64             die();
 65         }
 66         $pid = pcntl_fork();
 67         if ($pid < 0) {
 68             $this->_message("Fork fail");
 69             die();
 70         } elseif ($pid > 0) {
 71             exit(0);
 72         }
 73         
 74         if (posix_setsid() === -1) {
 75             $this->_message("Could not detach");
 76             die(); //獲取會話控制權
 77         }
 78 //        $pid = pcntl_fork();
 79 //        if ($pid < 0) {
 80 //            $this->_message("Fork fail");
 81 //            die();
 82 //        } elseif ($pid > 0) {
 83 //            exit(0);
 84 //        }
 85 //        fclose(STDIN);
 86 //        fclose(STDOUT);
 87 //        fclose(STDERR);
 88         chdir("/");
 89         umask(0);
 90 
 91         $fp = fopen($this->_pidFile, 'w') or die("Can't create pid file");
 92         fwrite($fp, posix_getpid());
 93         fclose($fp);
 94 
 95         //進入守護進程,處理任務
 96         if (!empty($this->_jobs)) {
 97             foreach ($this->_jobs as $job) {
 98                 call_user_func($job["function"], $job["argv"]);
 99             }
100         }
101         //file_put_contents("/root/wangli/test/daemon/Daemon_pid_log.log", "qqqqqqqqqqq");
102         return;
103     }
104 
105     private function start() {//程序啟動
106         //1.啟動前判斷是否已經存在一個進程
107         //2.沒有則創建一個守護進程
108         if ($this->_getPid() > 0) {
109             $this->_message("Is Running");
110             exit();
111         }
112         $this->daemonize();
113         $this->_message('Start');
114     }
115 
116     private function stop() {//停止守護進程
117         $pid = $this->_getPid();
118         if ($pid > 0) {
119             posix_kill($pid, SIGTERM);
120             unlink($this->_pidFile);
121             $this->_message("Stopped");
122         } else {
123             $this->_message("Not Running");
124         }
125     }
126     
127     private function status() {
128             if ($this->_getPid() > 0) {
129                 $this->_message('Is Running');
130             } else {
131                 $this->_message('Not Running');
132             }
133     }    
134 
135     public function main($argv) {//程序控制台
136         $param = is_array($argv) && count($argv) == 2 ? $argv[1] : null;
137         switch ($param) {
138             case 'start' :
139                 $this->start();
140                 break;
141             case 'stop':
142                 $this->stop();
143                 break;
144             case 'status':
145                 $this->status();
146                 break;
147             default :
148                 echo "Argv start|stop|status" . PHP_EOL;
149         }
150     }
151 
152     public function addJobs($jobs) {
153         if (!isset($jobs['function']) || empty($jobs['function'])) {
154             $this->_message('Need function param');
155         }
156 
157         if (!isset($jobs['argv']) || empty($jobs['argv'])) {
158             $jobs['argv'] = "";
159         }
160         $this->_jobs[] = $jobs;
161     }
162 
163 }
164 
165 $daemon = new Daemon(__FILE__,true);
166 $daemon->addJobs(array(
167     'function' => 'recordServerData',
168     'argv' => 'GOGOGO'
169 ));
170 
171 $daemon->main($argv);
172 
173 
174 function recordServerData($param) {
175     $i = 0;
176     while (true) {
177             $status=get_used_status(); //獲取服務器情況的函數
178             file_put_contents('/root/wangli/test/daemon/server.log', $status["detection_time"]." cpu_usage:".$status["cpu_usage"].PHP_EOL, FILE_APPEND);
179             sleep(5);
180     }
181 }
182 
183 
184 function get_used_status() {
185     $fp = popen('top -b -n 2 | grep -E "^(Cpu|Mem|Tasks)"', "r"); //獲取某一時刻系統cpu和內存使用情況
186     $rs = "";
187     while (!feof($fp)) {
188         $rs .= fread($fp, 1024);
189     }
190     pclose($fp);
191     $sys_info = explode("\n", $rs);
192 
193     $tast_info = explode(",", $sys_info[3]); //進程 數組
194     $cpu_info = explode(",", $sys_info[4]);  //CPU占有量  數組
195     $mem_info = explode(",", $sys_info[5]); //內存占有量 數組
196     //正在運行的進程數
197     $tast_running = trim(trim($tast_info[1], 'running'));
198 
199 
200     //CPU占有量
201     $cpu_usage = trim(trim($cpu_info[0], 'Cpu(s): '), '%us');  //百分比
202     //內存占有量
203     $mem_total = trim(trim($mem_info[0], 'Mem: '), 'k total');
204     $mem_used = trim($mem_info[1], 'k used');
205     $mem_usage = round(100 * intval($mem_used) / intval($mem_total), 2);  //百分比
206 
207 
208 
209     $fp = popen('df -lh | grep -E "^(/)"', "r");
210     $rs = fread($fp, 1024);
211     pclose($fp);
212     $rs = preg_replace("/\s{2,}/", ' ', $rs);  //把多個空格換成 “_”
213     $hd = explode(" ", $rs);
214     $hd_avail = trim($hd[3], 'G'); //磁盤可用空間大小 單位G
215     $hd_usage = trim($hd[4], '%'); //掛載點 百分比
216     //print_r($hd);
217     //檢測時間
218     $fp = popen('date +"%Y-%m-%d %H:%M"', "r");
219     $rs = fread($fp, 1024);
220     pclose($fp);
221     $detection_time = trim($rs);
222 
223 
224     return array('cpu_usage' => $cpu_usage, 'mem_usage' => $mem_usage, 'hd_avail' => $hd_avail, 'hd_usage' => $hd_usage, 'tast_running' => $tast_running, 'detection_time' => $detection_time);
225 }
226 ?>    
View Code

 

    
  
 
 
 
 
 
 
參考文獻:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM