深入探究PHP的多進程編程方法_php技巧

來源:互聯網
上載者:User

子進程的建立
一般的子進程的寫法是:

<?php$pid = pcntl_fork();if($pid == -1){     //建立失敗     die('could not fork');}else{    if($pid){        //從這裡開始寫的代碼是父進程的        exit("parent!");    }    else{        //子進程代碼,為防止不停的啟用子進程造成系統資源被耗盡的情況,一般子進程代碼運行完成後,加入exit來確保子進程正常退出。        exit("child");    }}?>

    上邊的代碼如果建立子進程成功的話,系統就有了2個進程,一個為父進程,一個為子進程,子進程的id號為$pid。在系統運行到$pid = pcntl_fork();時,在這個地方進行分支,父子進程各自開始運行各自的程式碼。代碼的運行結果是parent 和child,很奇怪吧,為什麼一個if和else互斥的代碼中,都輸出了結果?其實是像上邊所說的,代碼在pcntl_fork時,一個父進程運行parent,一個子進程運行了child。在代碼結果上就顯示了parent和child。至於誰先誰後的問題,這得要看系統資源的分配了。

    如果需要起多個進程來處理資料,可以根據資料的數量,按照約定好的數量比如說1000條一個進程來起子進程。使用for迴圈就可以了。   

 #如果獲得的總數小於或等於0,等待60秒,並退出  if ($count <= 0)   {    sleep(60);    exit;  }  #如果大於1000,計算需要起的進程數  if ($count > 1000)  {    $cycleSize = ceil($count/1000);  }  else  {    $cycleSize = 1;  }    for ($i=0; $i<$cycleSize; $i++)  {    $pid  = pcntl_fork();    if($pid == -1)    {      break;    }    else    {      if($pid)      {        #父進程獲得子進程的pid,存入數組        $pidArr[] = $pid;      }      else      {        //開始發送,子進程執行完自己的任務後,退出。          exit;      }    }  }    while(count($pidArr) > 0)  {    $myId  = pcntl_waitpid(-1, $status, WNOHANG);    foreach($pidArr as $key => $pid)    {      if($myId == $pid) unset($pidArr[$key]);    }  }

    然後使用crontab,來使此PHP程式每隔一段時間自動執行。

    當然,範例程式碼比較簡單,具體還需要考慮怎麼防止多個子進程執行到同一條資料或者當前進程處理資料未完成時,crontab又開始執行PHP檔案啟用新的進程等等。


PHP多進程實現方式
下面來系統地整理一下PHP多進程的實現方式:

1. 直接方式

pcntl_fork() 建立一個進程,在父進程傳回值是子進程的pid,在子進程傳回值是0,-1表示建立進程失敗。跟C非常相似。

測試指令碼 test.php
 

<?php  // example of multiple processes  date_default_timezone_set( 'Asia/Chongqing');  echo "parent start, pid ", getmypid(), "\n" ;  beep();  for ($i=0; $i<3; ++$i){     $pid = pcntl_fork();      if ($pid == -1){         die ("cannot fork" );     } else if ($pid > 0){         echo "parent continue \n";         for ($k=0; $k<2; ++$k){           beep();        }     } else if ($pid == 0){         echo "child start, pid ", getmypid(), "\n" ;         for ($j=0; $j<5; ++$j){           beep();        }         exit ;     }  }  // ***  function beep(){      echo getmypid(), "\t" , date( 'Y-m-d H:i:s', time()), "\n" ;     sleep(1);  }?>

用命令列運行

#php -f test.php

輸出結果

parent start, pid 17931793  2013-01-14 15:04:17parent continue1793  2013-01-14 15:04:18child start, pid 17941794  2013-01-14 15:04:181794  2013-01-14 15:04:191793  2013-01-14 15:04:191794  2013-01-14 15:04:20parent continue1793  2013-01-14 15:04:20child start, pid 17951795  2013-01-14 15:04:2017931794        2013-01-14 15:04:212013-01-14 15:04:211795  2013-01-14 15:04:211794  2013-01-14 15:04:221795  2013-01-14 15:04:22parent continue1793  2013-01-14 15:04:22child start, pid 17961796  2013-01-14 15:04:221793  2013-01-14 15:04:231796  2013-01-14 15:04:231795  2013-01-14 15:04:231795  2013-01-14 15:04:241796  2013-01-14 15:04:241796  2013-01-14 15:04:251796  2013-01-14 15:04:26

從中看到,建立了3個子進程,和父進程一起並行運行。其中有一行格式跟其他有些不同,
17931794                2013-01-14 15:04:212013-01-14 15:04:21
因為兩個進程同時進行寫操作,造成了衝突。


2. 阻塞方式

用直接方式,父進程建立了子進程後,並沒有等待子進程結束,而是繼續運行。似乎這裡看不到有什麼問題。如果php指令碼並不是運行完後自動結束,而是常駐記憶體的,就會造成子進程無法回收的問題。也就是殭屍進程。可以通過pcntl_wai()方法等待進程結束,然後回收已經結束的進程。
將測試指令碼改成:
 

$pid = pcntl_fork();if ($pid == -1){  ...} else if ($pid > 0){   echo "parent continue \n";   pcntl_wait($status);   for ($k=0; $k<2; ++$k){     beep();  }} else if ($pid == 0){   ...}

用命令列運行

#php -f test.php

輸出結果

parent start, pid 18071807  2013-01-14 15:20:05parent continuechild start, pid 18081808  2013-01-14 15:20:061808  2013-01-14 15:20:071808  2013-01-14 15:20:081808  2013-01-14 15:20:091808  2013-01-14 15:20:101807  2013-01-14 15:20:111807  2013-01-14 15:20:12parent continuechild start, pid 18091809  2013-01-14 15:20:131809  2013-01-14 15:20:141809  2013-01-14 15:20:151809  2013-01-14 15:20:161809  2013-01-14 15:20:171807  2013-01-14 15:20:181807  2013-01-14 15:20:19child start, pid 18101810  2013-01-14 15:20:20parent continue1810  2013-01-14 15:20:211810  2013-01-14 15:20:221810  2013-01-14 15:20:231810  2013-01-14 15:20:241807  2013-01-14 15:20:251807  2013-01-14 15:20:26

父進程在pcntl_wait()將自己阻塞,等待子進程運行完了才接著運行。


3. 非阻塞方式

阻塞方式失去了多進程的並行性。還有一種方法,既可以回收已經結束的子進程,又可以並行。這就是非阻塞的方式。
修改指令碼:
 

<?php  // example of multiple processes  date_default_timezone_set( 'Asia/Chongqing');  declare (ticks = 1);  pcntl_signal(SIGCHLD, "garbage" );  echo "parent start, pid ", getmypid(), "\n" ;  beep();  for ($i=0; $i<3; ++$i){     $pid = pcntl_fork();      if ($pid == -1){         die ("cannot fork" );     } else if ($pid > 0){         echo "parent continue \n";         for ($k=0; $k<2; ++$k){           beep();        }     } else if ($pid == 0){         echo "child start, pid ", getmypid(), "\n" ;         for ($j=0; $j<5; ++$j){           beep();        }         exit (0);     }  }  // parent  while (1){      // do something else     sleep(5);  }  // ***  function garbage($signal){      echo "signel $signal received\n" ;            while (($pid = pcntl_waitpid(-1, $status, WNOHANG))> 0){         echo "\t child end pid $pid , status $status\n" ;     }  }  function beep(){      echo getmypid(), "\t" , date( 'Y-m-d H:i:s', time()), "\n" ;     sleep(1);  }?>

用命令列運行

#php -f test.php &

輸出結果

parent start, pid 20662066  2013-01-14 16:45:34parent continue2066  2013-01-14 16:45:35child start, pid 20672067  2013-01-14 16:45:3520662067        2013-01-14 16:45:362013-01-14 16:45:362067  2013-01-14 16:45:37parent continue2066  2013-01-14 16:45:37child start, pid 20682068  2013-01-14 16:45:372067  2013-01-14 16:45:382068  2013-01-14 16:45:382066  2013-01-14 16:45:38parent continue2066  2013-01-14 16:45:40child start, pid 20692069  2067  2013-01-14 16:45:402013-01-14 16:45:402068  2013-01-14 16:45:402066  2013-01-14 16:45:412069  2013-01-14 16:45:412068  2013-01-14 16:45:41signel 17 received     child end pid 2067, status 02069  2013-01-14 16:45:422068  2013-01-14 16:45:422069  2013-01-14 16:45:43signel 17 received     child end pid 2068, status 02069  2013-01-14 16:45:44signel 17 received     child end pid 2069, status 0

多個進程又並行運行了,而且運行大約10秒鐘之後,用 ps -ef | grep php 查看正在啟動並執行進程,只有一個進程
lqling    2066  1388  0 16:45 pts/1    00:00:00 php -f t5.php
是父進程,子進程被回收了。


子進程退出狀態

pcntl_waitpid(-1, $status, WNOHANG) $status

 返回子進程的結束狀態


windows下多線程

windows系統不支援pcntl函數,幸好有curl_multi_exec()這個工具,利用內部的多線程,訪問多個連結,每個連結可以作為一個任務。

編寫指令碼 test1.php
 

<?php  date_default_timezone_set( 'Asia/Chongqing');  $tasks = array(     'http://localhost/feedbowl/t2.php?job=task1',     'http://localhost/feedbowl/t2.php?job=task2',     'http://localhost/feedbowl/t2.php?job=task3'  );  $mh = curl_multi_init();  foreach ($tasks as $i => $task){     $ch[$i] = curl_init();     curl_setopt($ch[$i], CURLOPT_URL, $task);     curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1);     curl_multi_add_handle($mh, $ch[$i]);  }  do {$mrc = curl_multi_exec($mh,$active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);  while ($active && $mrc == CURLM_OK) {     if (curl_multi_select($mh) != -1) {      do {$mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);     }  }  // completed, checkout result  foreach ($tasks as $j => $task){     if (curl_error($ch[$j])){       echo "task ${j} [$task ] error " , curl_error($ch[$j]), "\r\n" ;     } else {       echo "task ${j} [$task ] get: \r\n" , curl_multi_getcontent($ch[$j]), "\r\n" ;     }  }?>

編寫指令碼 test2.php
 

<?php  date_default_timezone_set( 'Asia/Chongqing');  echo "child start, pid ", getmypid(), "\r\n" ;  for ($i=0; $i<5; ++$i){     beep();  }  exit (0);  // ***  function beep(){    echo getmypid(), "\t" , date('Y-m-d H:i:s' , time()), "\r\n";    sleep(1);  }?>

用命令列運行

#php -f test1.php &

輸出結果

task 0 [http://localhost/feedbowl/t2.php?job=task1] get:child start, pid 58045804  2013-01-15 20:22:355804  2013-01-15 20:22:365804  2013-01-15 20:22:375804  2013-01-15 20:22:385804  2013-01-15 20:22:39task 1 [http://localhost/feedbowl/t2.php?job=task2] get:child start, pid 58045804  2013-01-15 20:22:355804  2013-01-15 20:22:365804  2013-01-15 20:22:375804  2013-01-15 20:22:385804  2013-01-15 20:22:39task 2 [http://localhost/feedbowl/t2.php?job=task3] get:child start, pid 58045804  2013-01-15 20:22:355804  2013-01-15 20:22:365804  2013-01-15 20:22:375804  2013-01-15 20:22:385804  2013-01-15 20:22:39

從列印的時間看到,多個任務幾乎是同時啟動並執行。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.