浅谈PHP进程管理


Posted in PHP onMarch 08, 2019

这篇文章是对之前一篇文章的补充和改进, 创建一个主(master)进程,主进程安装定时器,每隔5分钟检测一次队列长度,根据队列长度计算需要的worker进程,

然后创建或者杀掉子进程。这样做的好处是防止队列堆积,任务得不到及时处理。更新业务代码,只需要reload操作即可。

整个流程有以下知识点:

创建守护进程的步骤:

  1. 设置默认文件权限
  2. fork一个进程,父进程退出
  3. 调用setsid创建一个新的会话
  4. 将当前工作目录更改为根目录
  5. 关闭不再需要的文件描述符

使用信号实现定时器
上一篇定时器依赖于系统的定时任务,这次使用闹钟信号实现,php 5.3.0以下的版本依赖于ticks,5.3.0及以上版本可使用pcntl_signal_dispatch

信号:提供了一种异步事件处理的方法,在某个信号出现时,进程有以下三种方式对信号进行处理

  1. 忽略此信号
  2. 捕捉信号
  3. 执行系统默认动作,大多数信号的默认动作是终止该进程

常见信号
SIGKILL,SIGSTOP是两种不能被用户忽略和捕捉的信号

SIGINT(2):程序终止信号,通常是Ctrl-C)时发出,用于通知前台进程组终止进程

SIGQUIT(3):和SIGINT类似, 但由QUIT字符(通常是Ctrl+/)来控制. 进程收到该消息退出时会产生core文件

SIGKILL(9):立即终止进程,不可被忽略捕捉或阻塞

SIGUSR1(10):用户定义信号

SIGUSR2(12):留给用户使用

SIGALRM(14):闹钟信号

SIGTERM(15):终止进程,可被程序捕捉,使得进程可以执行完清理操作。

SIGSTOP(19):停止一个进程,该进程还未结束, 只是暂停执行

防止产生僵尸进程
所有的进程在退出的时候都会成为僵尸进程,这时候如果父进程还在运行,没有调用wait或者waitpid,则僵尸进程占用的资源不会被清理,如果父进程已终止,僵尸进程由init进程进行清理。

抽调业务代码,主要代码如下

其中要注意的一点,创建守护进程关闭输入输出,错误输出流的时候,如果代码后面有echo等输出字符,将出现致命错误,需要在php代码中重定向输出流到/dev/null。或者在终端启动进程的时候进行重定向

<?php
define('PROC_MAX', 10);
define('PROC_MIN', 5);
 
$cmd = $argv[1];
$aPid = [];
$pidFile = __DIR__ . '/pid.pid';
$pid = file_get_contents($pidFile);
 
switch($cmd){
 case 'start' :
  if(posix_kill($pid, 0)){
   echo "gamelog process is already exsits!\n";
   return false;
  }
  //设置默认文件权限
  umask(022);
  //fork
  $pid = pcntl_fork();
  if($pid < 0){
   exit('fork error!');
  }else if($pid > 0){
   exit;
  }
  //脱离当前终端
  posix_setsid();
  //将当前工作目录更改为根目录
  chdir('/');
  //关闭文件描述符
  fclose(STDIN);
  fclose(STDOUT);
  fclose(STDERR);
  //重定向输入输出
  global $STDOUT, $STDERR;
  $STDOUT = fopen('/dev/null', 'a');
  $STDERR = fopen('/dev/null', 'a');
   
  cli_set_process_title('gamelog:master');
  $pid = posix_getpid();
  file_put_contents($pidFile, $pid);
  //闹钟信号
  pcntl_signal(SIGALRM, function() use (&$aPid) {
   pcntl_alarm(300);
   $workerNum = mt_rand(1, 20);//此处检测你需要的进程数
   $daemonNum = count($aPid);
    
   ($workerNum > PROC_MAX) && ($workerNum = PROC_MAX);
   if($daemonNum < $workerNum){
    $procNum = $workerNum - $daemonNum;
    $procNum = max(PROC_MIN, $procNum);
    for($p = 1; $p <= $procNum; $p++){
     $pid = pcntl_fork();
     if ($pid < 0) {
      exit('fork error!');
     } else if ($pid == 0) {
      cli_set_process_title('gamelog:worker');
      while (true) {
       //do your work
       usleep(100);
      }
      exit();
     } else {
      $aPid[] = $pid;
     }
    }
   }else if($daemonNum > $workerNum){
    $wokerNum = max($wokerNum, PROC_MIN);
    $killNum = $daemonNum - $workerNum;
    foreach($aPid as $key=>$pid){
     if(posix_kill($pid, SIGKILL)){
      unset($aPid[$key]);
      if(--$killNum <= 0){
       break;
      }
     }
    }
   }
  }, false);
   
  pcntl_signal(SIGUSR1, function() use (&$aPid, $pid){
   foreach($aPid as $key=>$chpid){
    if(!posix_kill($chpid, SIGKILL)){
     echo "kill child $chpid faild\n";
    }
   }
   posix_kill($pid, SIGKILL);
  }, false);
   
  pcntl_signal(SIGUSR2, function() use (&$aPid, $pid){
   foreach($aPid as $key=>$chpid){
    if(!posix_kill($chpid, SIGKILL)){
     echo "kill child $chpid faild\n";
    }
   }
   if(!posix_kill($pid, SIGALRM)){
    echo "restart gamelog faild\n";
   }
  }, false);
   
  posix_kill($pid, SIGALRM);
  while (true) {
   pcntl_signal_dispatch();
   $pid = pcntl_wait($status, WUNTRACED);//不阻塞
  }
  break;
  
 case 'stop' :
  if(!posix_kill($pid, SIGUSR1)){
   exit('stop gamelog process error!');
  }
  break;
 case 'reload' :
  if(!posix_kill($pid, SIGUSR2)){
   exit('restop gamelog process error!');
  }
  break;
 default :
  echo "Useage php signal.php start|stop|reload\n";
}

以上所述是小编给大家介绍的PHP进程管理详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

PHP 相关文章推荐
用PHP调用Oracle存储过程的方法
Sep 12 PHP
将一维或多维的数组连接成一个字符串的php代码
Aug 08 PHP
php数组函数序列之prev() - 移动数组内部指针到上一个元素的位置,并返回该元素值
Oct 31 PHP
php采用ajax数据提交post与post常见方法总结
Nov 10 PHP
php+mysql大量用户登录解决方案分析
Dec 29 PHP
php中curl使用指南
Feb 05 PHP
linux下实现定时执行php脚本
Feb 13 PHP
php二维数组合并及去重复的方法
Mar 04 PHP
php获取指定(访客)IP所有信息(地址、邮政编码、国家、经纬度等)的方法
Jul 06 PHP
php实现mysql数据库连接操作及用户管理
Nov 08 PHP
php简单统计在线人数的方法
May 10 PHP
PHP数组遍历的几种常见方式总结
Feb 15 PHP
PHP多进程通信-消息队列使用
Mar 08 #PHP
浅谈PHP匿名函数和闭包
Mar 08 #PHP
使用PHPUnit进行单元测试并生成代码覆盖率报告的方法
Mar 08 #PHP
ThinkPHP中图片按比例切割的代码实例
Mar 08 #PHP
PHP的微信支付接口使用方法讲解
Mar 08 #PHP
PHP实现会员账号单唯一登录的方法分析
Mar 07 #PHP
PHP模糊查询技术实例分析【附源码下载】
Mar 07 #PHP
You might like
PHP设计模式之装饰者模式代码实例
2015/05/11 PHP
PHPStrom 新建FTP项目以及在线操作教程
2016/10/16 PHP
为jquery.ui.dialog 增加“自动记住关闭时的位置”的功能
2009/11/24 Javascript
超酷的网页音乐播放器DewPlayer使用方法
2010/12/18 Javascript
ajax不执行success回调而是执行了error回调
2012/12/10 Javascript
jQuery表单获取和失去焦点输入框提示效果的实例代码
2013/08/01 Javascript
jqgrid 编辑添加功能详细解析
2013/11/08 Javascript
jQuery自带的一些常用方法总结
2014/09/03 Javascript
jQuery中;function($,undefined) 前面的分号的用处
2014/12/17 Javascript
JavaScript中DOM详解
2015/04/13 Javascript
JS实现简单的图书馆享元模式实例
2015/06/30 Javascript
如何使用jQuery技术开发ios风格的页面导航菜单
2015/07/29 Javascript
详解JavaScript中的4种类型识别方法
2015/09/14 Javascript
js文字横向滚动特效
2015/11/11 Javascript
javascript拖拽应用实例(二)
2016/03/25 Javascript
JavaScript Math 对象常用方法总结
2016/04/28 Javascript
浅谈JavaScript 执行环境、作用域及垃圾回收
2016/05/31 Javascript
如何使用Jquery动态生成二级选项列表
2020/02/06 jQuery
python 简单搭建阻塞式单进程,多进程,多线程服务的实例
2017/11/01 Python
使用matplotlib画散点图的方法
2018/05/25 Python
解决Python安装后pip不能用的问题
2018/06/12 Python
python实现黑客字幕雨效果
2018/06/21 Python
PyCharm设置SSH远程调试的方法
2018/07/17 Python
python操作小程序云数据库实现简单的增删改查功能
2019/06/06 Python
python语言基本语句用法总结
2019/06/11 Python
python爬虫容易学吗
2020/06/02 Python
浅谈HTML5 &amp; CSS3的新交互特性
2016/07/19 HTML / CSS
阿联酋彩妆品牌:OUD MILANO
2019/10/06 全球购物
英国著名药妆店:Superdrug
2021/02/13 全球购物
The North Face意大利官网:服装、背包和鞋子
2020/06/17 全球购物
Linux内核产生并发的原因
2016/11/08 面试题
教师党员学习十八届四中全会思想汇报
2014/11/03 职场文书
十岁生日答谢词
2015/01/05 职场文书
2016年小学生新年寄语
2015/08/18 职场文书
村党总支部公开承诺书2016
2016/03/25 职场文书
详解Redis集群搭建的三种方式
2021/05/31 Redis