首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

聊聊 PHP 多进程模式下的孤儿进程和僵尸进程

编程知识
2024年08月22日 08:20

大家好,我是码农先森。

在 PHP 的编程实践中多进程通常都是在 cli 脚本的模式下使用,我依稀还记得在多年以前为了实现从数据库导出千万级别的数据,第一次在 PHP 脚本中采用了多进程编程。在此之前我从未接触过多进程,只知道 PHP-FPM 进程管理器是多进程模型,但从未在编程中进行实践。多进程虽然能带来效率上的提升,但依然会带来不少的问题,如果初学者使用多进程,那注定会遇到各种奇奇怪怪的 Bug 比如并发操作数据库引起死锁、共用内存变量资源造成串数据、忘记回收进程资源导致产生孤儿进程、僵尸进程等。反正如果我们长期都是 PHP-FPM 模式下编程的话,在使用多进程编程时需要慎之又慎,避免出现意想不到的问题。不过这次我想分享的内容是多进程模式下的孤儿进程和僵尸进程,通过示例代码来看看这两者进程是如何产生的,又应该如何解决,内容不难但是在实际的编程中是可能比较容易忽视的点。

按照惯例我们先看看孤儿进程和僵尸进程的基础概念。

  • 孤儿进程:是指一个进程的父进程已经终止,但该子进程仍然在运行。当父进程结束时,操作系统会将其所有的子进程重新分配给 init 进程。init 进程会负责这些孤儿进程,并确保它们能够正确结束。孤儿进程不会造成资源泄漏,因为最终它们会被 init 进程管理并正确清理。
  • 僵尸进程:是指一个已经完成执行的进程,但仍在进程表中保留了一些信息。这通常发生在父进程未调用 wait() 或相关函数来获取子进程的退出状态时。僵尸进程处于 Z 状态,是一种占用系统资源但不占用 CPU 的进程。僵尸进程会继续占用系统的进程 ID,如果大量产生将导致进程 ID 耗尽,可能会影响系统的正常运行。

这两者进程的基础概念应该还比较好理解,孤儿进程的产生就是缘于父进程的不负责,自己先跑路了,导致自己的子进程变成了孤儿,最后孤儿进程被系统给回收了,可以理解为被政府的福利院收养了。僵尸进程的产生就是儿子进程执行完了没有退出,但是父进程又不知情,无法及时回收儿子进程的资源,导致自己的儿子进程变成了僵尸进程,僵尸进程往往比孤儿进程对系统的危害更大,接下来我们来看看具体的代码示例。

首先看看孤儿进程示例,使用 pcntl_fork 函数创建了一个子进程,子进程会每间隔 1 秒钟获取一次自己进程的 ID 和父进程的 ID,而父进程在 2 秒钟之后就退出跑路了,自此子进程就变成了孤儿进程,被系统进程收养了。

<?php

// 孤儿进程示例

$pid = pcntl_fork();
if ($pid < 0) {
   exit('fork error');
} else if($pid > 0) {
   // 父进程执行空间 ...
   // getmypid 函数获取当前父进程ID
   echo "父进程ID: " . getmypid() . PHP_EOL;

   // 2 秒之后退出当前的父进程
   // 父进程先行跑路了
   sleep(2);
   exit();
}

// 子进程执行空间 ...
// getmypid 函数获取当前子进程ID
$cid = getmypid();
echo "当前子进程: {$cid}" . PHP_EOL;

// 每隔 1 秒获取一下进程ID
for($i = 1; $i <= 10; $i++){
    // posix_getppid 函数获取当前子进程的父进程ID
    sleep(1);
    echo "当前子进程ID: " . $cid. ", 父进程ID: " . posix_getppid() . PHP_EOL;
}

// 由于父进程跑路了,子进程变成了孤儿进程 ...

执行 php index.php 观察输出结果,可以看出间隔一段时间之后父进程的 ID 就变成 1 了,即为系统进程。

## 执行程序
[manongsen@root php_test]$ php index.php 
父进程ID: 3484
当前子进程: 3485
当前子进程ID: 3485, 父进程ID: 3484
当前子进程ID: 3485, 父进程ID: 3484
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1
当前子进程ID: 3485, 父进程ID: 1

然后再看看僵尸进程示例,同样也使用 pcntl_fork 创建了一个子进程,然后子进程先行执行完了,父进程还未执行完,这时子进程变成为了僵尸进程。当然僵尸进程也不会一直存在,如果父进程退出了其也会结束自身进程,反之就会一直存在占用着系统资源。

<?php

// 僵尸进程示例

$pid = pcntl_fork();
if ($pid < 0) {
   exit('fork error');
} else if($pid > 0) {
   // 父进程执行空间 ...
   // getmypid 函数获取当前父进程ID
   echo "父进程ID: " . getmypid() . PHP_EOL;

   // 120 秒之后退出当前的父进程
   sleep(120);
   exit();
}

// 子进程执行空间 ...
// getmypid 函数获取当前子进程ID
$cid = getmypid();
echo "当前子进程: {$cid}" . PHP_EOL;

// 10 秒之后退出子进程
sleep(10);

执行 php index.php 观察输出结果,通过查看子进程信息中有一个 Z+ 标识,则表示该进程已经成为了僵尸进程。

## 执行程序
[manongsen@root php_test]$ php index.php 
父进程ID: 85804
当前子进程: 85805

## 查看进程信息
[manongsen@root php_test]$ ps aux | grep 85805
root             90776   0.0  0.0 408169072   1408 s060  U+    22:06下午   0:00.00 grep 85805
root             85805   0.0  0.0         0      0 s062  Z+    22:06下午   0:00.00 (php)

最后来看看正常进程的示例,也先使用 pcntl_fork 创建了一个子进程,但与上面两个例子不同的是在其父进程中会调用 pcntl_wait 函数一直等待子进程结束。在子进程 10 秒钟过后,父进程会接受到子进程执行完毕的通知,然后回收子进程的资源。

<?php

// 正常进程示例

$pid = pcntl_fork();
if ($pid < 0) {
   exit('fork error');
} else if($pid > 0) {
    // 父进程执行空间 ...
    // getmypid 函数获取当前父进程ID
    echo "父进程ID: " . getmypid() . PHP_EOL;

    // 一直等待到子进程结束后回收资源
    $cid = pcntl_wait($status);
    echo "父进程ID: " . getmypid() . ", 接收到子进程ID: {$cid} 退出" . PHP_EOL;
    exit();
}

// 子进程执行空间 ...
// getmypid 函数获取当前子进程ID
$cid = getmypid();
echo "当前子进程: {$cid}" . PHP_EOL;

// 睡眠 10 秒
sleep(10);

执行 php index.php 观察输出结果,可以看出子进程执行完毕之后,父进程接收到了子进程的通知。

## 执行程序
[manongsen@root php_test]$ php index.php 
父进程ID: 49954
当前子进程: 49955
父进程ID: 49954, 接收到子进程ID: 49955 退出

## 查看进程 49955
[manongsen@root php_test]$ ps aux | grep 49955
root             19516   0.0  0.0 407972944   1216 s062  R+    22:23下午   0:00.00 grep 49955
root             49955   0.0  0.0 437931336    372 s060  S+    22:23下午   0:00.00 php index.php

## 再次查看进程 49955
[manongsen@root php_test]$ ps aux | grep 49955
root             26599   0.0  0.0 407963440    480 s062  R+    22:24下午   0:00.00 grep 49955

通过这上面的例子可以看出,多进程中正确的使用方式是要在父进程中使用 pcntl_wait 函数等待子进程的结束,而不是只管 pcntl_fork 生产完子进程,然后就对子进程不闻不问了。从生活化的例子来说就是,你不能只管生娃,生完之后就不管养育了,这种操作肯定是不行的,道德和法律层面这一关你都过不去。利用 pcntl_wait 这个函数可以很优雅的解决了孤儿进程和僵尸进程,但在实际的编程中很容易忽视这一点,因此这一点值得注意。本次分享的内容就到这里了,希望对大家能有所帮助。

感谢阅读,个人观点仅供参考,欢迎在评论区发表不同观点。


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

From:https://www.cnblogs.com/yxhblogs/p/18337236
本文地址: http://www.shuzixingkong.net/article/1319
0评论
提交 加载更多评论
其他文章 一个能够生成 Markdown 表格的 Bash 脚本
哈喽大家好,我是咸鱼。 今天分享一个很实用的 bash 脚本,可以通过手动提供单元格内容和列数或者将带有分隔符的文件(如 CSV、TSV 文件)转换为 Markdown 表格。 源代码在文末哦!原文链接:https://josh.fail/2022/pure-bash-markdown-table-
从源码分析 SpringBoot 的 LoggingSystem → 它是如何绑定日志组件的
开心一刻 今天心情不好,想约哥们喝点 我:心情不好,给你女朋友说一声,来我家,过来喝点 哥们:行!我给她说一声 我:你想吃啥?我点外卖 哥们:你俩定吧,我已经让她过去了 我:???我踏马让你过来!和她说一声 哥们:哈哈哈,我踏马寻思让她过去呢 前情回顾 SpringBoot2.7 霸王硬上弓 Log
从源码分析 SpringBoot 的 LoggingSystem → 它是如何绑定日志组件的 从源码分析 SpringBoot 的 LoggingSystem → 它是如何绑定日志组件的 从源码分析 SpringBoot 的 LoggingSystem → 它是如何绑定日志组件的
DDD建模后写代码的正确姿势(Java、dotnet双平台)
本文书接上回《一种很变态但有效的DDD建模沟通方式》,关注公众号(老肖想当外语大佬)获取信息: 最新文章更新; DDD框架源码(.NET、Java双平台); 加群畅聊,建模分析、技术交流; 视频和直播在B站。 终于到了写代码的环节 如果你已经阅读过本系列前面的所有文章,我相信你对需求分析和建模设计有
DDD建模后写代码的正确姿势(Java、dotnet双平台) DDD建模后写代码的正确姿势(Java、dotnet双平台) DDD建模后写代码的正确姿势(Java、dotnet双平台)
使用分布式锁解决IM聊天数据重复插入的问题
导航 业务背景 问题分析与定位 探索可行的解决方案 数据库层面处理——唯一索引 应用程序层面处理——分布式锁 分布式锁概述 分布式锁需要具备哪些特性? 分布式锁有哪些实现方式? 基于数据库的实现方式 基于Redisson实现方式 Redission介绍 概述 可重入锁 基于Redisson解决方案
使用分布式锁解决IM聊天数据重复插入的问题 使用分布式锁解决IM聊天数据重复插入的问题 使用分布式锁解决IM聊天数据重复插入的问题
零基础学习人工智能—Python—Pytorch学习(七)
前言 本文主要讲神经网络的下半部分。 其实就是结合之前学习的全部内容,进行一次神经网络的训练。 神经网络 下面是使用MNIST数据集进行的手写数字识别的神经网络训练和使用。 MNIST 数据集,是一个常用的手写数字识别数据集。MNIST 数据集包含 60,000 张 28x28 像素的灰度训练图像和
零基础学习人工智能—Python—Pytorch学习(七) 零基础学习人工智能—Python—Pytorch学习(七) 零基础学习人工智能—Python—Pytorch学习(七)
SLAB:华为开源,通过线性注意力和PRepBN提升Transformer效率 | ICML 2024
论文提出了包括渐进重参数化批归一化和简化线性注意力在内的新策略,以获取高效的Transformer架构。在训练过程中逐步将LayerNorm替换为重参数化批归一化,以实现无损准确率,同时在推理阶段利用BatchNorm的高效优势。此外,论文设计了一种简化的线性注意力机制,其在计算成本较低的情况下达到
SLAB:华为开源,通过线性注意力和PRepBN提升Transformer效率 | ICML 2024 SLAB:华为开源,通过线性注意力和PRepBN提升Transformer效率 | ICML 2024 SLAB:华为开源,通过线性注意力和PRepBN提升Transformer效率 | ICML 2024
[VS Code扩展]写一个代码片段管理插件(二):功能实现
@目录创建和插入代码片段代码片段列表代码片段预览代码片段编辑自定义映射默认映射自动完成项目地址 创建和插入代码片段 VS Code扩展提供了数据存储,其中globalState是使用全局存储的Key-Value方式来保存用户状态,支持在不同计算机上保留某些用户状态,详情请参考官方文档 若在编辑器区域
[VS Code扩展]写一个代码片段管理插件(二):功能实现 [VS Code扩展]写一个代码片段管理插件(二):功能实现 [VS Code扩展]写一个代码片段管理插件(二):功能实现
开发一个MutatingWebhook
介绍 Webhook就是一种HTTP回调,用于在某种情况下执行某些动作,Webhook不是K8S独有的,很多场景下都可以进行Webhook,比如在提交完代码后调用一个Webhook自动构建docker镜像 准入 Webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制。 可以定义两