• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

php多进程实例PHP多进程处理并行处理任务实例

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

  在前面的文章《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 
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
php去掉指定的html标签及内容(转)发布时间:2022-07-10
下一篇:
phpcomposer包管理工具发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap