文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

深入理解 PHP 高性能框架 Workerman 守护进程原理

2024-11-29 20:16

关注

守护进程顾名思义就是能够在后台一直运行的进程,不会霸占用户的会话终端,脱离了终端的控制。相信朋友们对这东西都不陌生了吧?如果连这个概念都还不能理解的话,建议回炉重造多看看 Linux 进程管理相关的基础知识。在我们日常的编程中常见有类似 php think ...、php artisan ...、php yii ... 等命令启动需要一直执行的任务,都会通过 nohup 挂载到后台保持长期运行的状态。同样在 Workerman 中也是使用类似 php index.php start 的命令来启动进程,但不同的是它不需要利用 nohup 便可以挂载到后台运行。那有些朋友就会好奇它是怎么实现的呢?为了解决朋友们的疑惑,我们今天就重点深入分析一下 Workerman 守护进程的实现原理。

我们先了解一些进程相关的知识:

这些概念俗称八股文,向来都不怎么好理解,那我们来看个例子。执行了命令 php index.php 便产生了进程 61052「该进程的父进程是 Bash 进程 8243,这里不用管它」,然后通过 Fork 创建了子进程 61053 且其父进程就是 61052,这两个进程拥有共同的进程组 61052 和会话 8243。调用 posix_setsid 函数,将会为子进程 61053 开启新的进程组 61053 和新的会话 61053,这里的会话可以理解为一个新的命令窗口终端。最后子进程 61053 通过 Fork 创建了子进程 61054,进程 61053 升级成了父进程,这里再次 Fork 的原因是要避免被终端控制进程所关联,这个进程 61052 是在终端的模式下创建的,自此进程 61054 就形成了守护进程。

[manongsen@root phpwork]$ php index.php
[parent] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243 
[parent1] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243 退出了该进程
[child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61052, 会话ID: 8243 
[child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 
[parent2] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 退出了该进程
[child2] 进程ID: 61054, 父进程ID: 61053, 进程组ID: 61053, 会话ID: 61053 保留了该进程

[manongsen@root phpwork]$ ps aux | grep index.php
root             66064   0.0  0.0 408105040   1472 s080  S+   10:00下午   0:00.00 grep index.php
root             61054   0.0  0.0 438073488    280   ??  S    10:00下午   0:00.00 php index.php

上面举例的进程信息,正是这段代码运行所产生的。如果看了这段代码且细心的朋友,会发现为什么 posix_setsid 这个函数不放在第一次 Fork 前调用,而在第二次 Fork 前调用呢,这样的话就不用 Fork 两次了?原因是组长进程是不能创建会话的,进程组ID 61052 和进程ID 61052 相同「即当前进程则为组长进程」,所以需要子进程来创建新的会话,这一点需要特别注意一下。

 0 ) {
    // [parent1] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243 退出了该进程
    echoMsg("parent1", "退出了该进程");
    exit;
}

// 创建的 子进程ID 为 61053 但 进程组、会话 还是和父进程是同一个
// [child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61052, 会话ID: 8243 
echoMsg("child1");

// 调用 posix_setsid 函数,会创建一个新的会话和进程组,并设置 进程组ID 和 会话ID 为该 进程ID
if (-1 === \posix_setsid()) {
    throw new Exception("Setsid fail");
}

// 现在会发现 进程组ID 和 会话ID 都变成了 61053 在这里相当于启动了一个类似 Linux 终端下的会话窗口
// [child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 
echoMsg("child1");

// 第二次 Fork 进程
// 这里需要二次 Fork 进程的原因是避免被终端控制进程所关联,这个进程 61052 是在终端的模式下创建的
// 需要脱离这个进程 61052 以确保守护进程的稳定
$pid = pcntl_fork();
if ( $pid  < 0 ){
    exit('fork error');
} else if( $pid > 0 ) {
    // [parent2] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 退出了该进程
    echoMsg("parent2", "退出了该进程");
    exit;
}

// 到这里该进程已经脱离了终端进程的控制,形成了守护进程
// [child2] 进程ID: 61054, 父进程ID: 61053, 进程组ID: 61053, 会话ID: 61053 保留了该进程
echoMsg("child2", "保留了该进程");

sleep(100);

有时间的朋友最好自行执行代码并分析一遍,会有不一样的收获。这里假装你已经实践过了,这下我们来看 Workerman 的 Worker.php 文件中 554 行的 runAll 方法中的 static::daemonize() 这个函数,实现的流程逻辑和上面的例子几乎一样。不过这里还使用了 umask 这个函数,其主要的作用是为该进程所创建的文件或目录赋予相应的权限,保证有权限操作文件或目录。

// workerman/Worker.php:554

public static function runAll()
{
    static::checkSapiEnv();
    static::init();
    static::parseCommand();
    static::lock();
    // 创建进程并形成守护进程
    static::daemonize();
    static::initWorkers();
    static::installSignal();
    static::saveMasterPid();
    static::lock(\LOCK_UN);
    static::displayUI();
    static::forkWorkers();
    static::resetStd();
    static::monitorWorkers();
}

// workerman/Worker.php:1262

protected static function daemonize()
{
 // 判断是否已经是守护状态、以及当前系统是否是 Linux 环境
    if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) {
        return;
    }
    
    // 设置 umask 为 0 则当前进程创建的文件权限都为 777 拥有最高权限
    \umask(0);
    
    // 第一次创建进程
    $pid = \pcntl_fork();
    if (-1 === $pid) {
     // 创建进程失败
        throw new Exception('Fork fail');
    } elseif ($pid > 0) {
     // 主进程退出
        exit(0);
    }

 // 子进程继续执行...
    // 调用 posix_setsid 函数,可以让进程脱离父进程,转变为守护进程
    if (-1 === \posix_setsid()) {
        throw new Exception("Setsid fail");
    }

 // 第二次创建进程,在基于 System V 的系统中,通过再次 Fork 父进程退出
 // 保证形成的守护进程,不会成为会话首进程,不会拥有控制终端
    $pid = \pcntl_fork();
    if (-1 === $pid) {
     // 创建进程失败
        throw new Exception("Fork fail");
    } elseif (0 !== $pid) {
     // 主进程退出
        exit(0);
    }

    // 子进程继续执行...
}

守护进程也是 Workerman 中重要的一部分,它保障了 Workerman 进程的稳定性。不像我们通过 nohup 启动的命令,挂起到后台之后,有时还神不知鬼不觉的就挂了,朋友们或许都有这样的经历吧。当然在市面上也有一些开源的守护进程管理软件,比如 supervisor 等,其次还有人利用会话终端 screen、tmux 等工具来实现。其实守护进程的实现方式有多种多样,我们这里只是为了分析 Workerman 中守护进程的实现原理,而引出了在 PHP 中实现守护进程模式的例子,希望本次的内容能对你有所帮助。

来源:码农先森内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯