在前面的文章《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的子进程状态
status
参数上;WNOHANG表示如果没有子进程退出立刻返回。函数的返回值$pid是一个Int值,如果是子进程退出了,表示子进程号;如果发生错误返回-1,如果提供了 WNOHANG
作为option,并且没有可用子进程时返回0。主要作用是防止产生僵尸进程。子进程结束时,父进程没有等待它(通过调用wait或者waitpid),那么子进程结束后不会释放所有资源,这就是僵尸进程。$pathname
, int $mode
)//创建一个管道(用于子进程之间的通讯,可以想象成文件,但是管道的读写都是自动上锁的,原子性操作)$filename
, string $mode)//打开管道
多进程的一个典型应用就是可以并行的去处理几件事情,比如并行的去请求几个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"); // 删除管道,已经没有作用了
示例,简单演示了多进程的创建和管道的创建,以及何为子进程空间。
一个进一步的例子,用来多进程请求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;
介绍一个多进程的用途,编写守护进程。
守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
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 ?>
下面再说明一个例子,利用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 ?>
原文:https://www.cnblogs.com/Andres/p/8934136.html