在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
在前面的文章《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表示如果没有子进程退出立刻返回。函数的返回值$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"); // 删除管道,已经没有作用了 示例,简单演示了多进程的创建和管道的创建,以及何为子进程空间。 一个进一步的例子,用来多进程请求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;
多进程的用途介绍一个多进程的用途,编写守护进程。
为什么开发守护进程?
很多程序以服务形式存在,他没有终端或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 ?>
下面再说明一个例子,利用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 全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论