• 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()."	 start ".$method."	at".$start.PHP_EOL;
    22             //sleep($time);
    23             $c = file_get_contents($method);
    24            // echo getmypid() ."
    ";
    25             $end = microtime(true);
    26             $cost = $end-$start;
    27             echo getmypid()."	 stop 	".$method."	at:".$end."	cost:".$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."	 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."	 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("
    ", $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
        
      
     
     
     
     
     
     
    参考文献:
    作者:Leven
    本博客主要记录个人工作和学习中的一些总结,经验和感悟。欢迎转载和评论,转载请给出原文链接。
    您也可以通过邮箱联系我:leven_developer#outlook.com
    如果文章对您有所帮助,您可以给我一点打赏,会让我更有动力做所从事的事情,非常感谢。
  • 相关阅读:
    监控Redis集群
    host主机监控规则
    Prometheus自身的监控告警规则
    Prometheus alerts 各种告警规则
    Elasticsearch官方文档离线访问实操指南
    Ceph 存储集群
    采用阿里云 yum的方式安装ceph
    设置HTTP请求自动跳转HTTPS
    jumpserver 2222端口的使用
    安装jumpserver 2.1.2版本遇到的坑
  • 原文地址:https://www.cnblogs.com/Andres/p/8934136.html
Copyright © 2020-2023  润新知