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

2024-08-23 16:20

本文主要是介绍聊聊 PHP 多进程模式下的孤儿进程和僵尸进程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在 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 函数获取当前父进程IDecho "父进程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 函数获取当前子进程的父进程IDsleep(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 函数获取当前父进程IDecho "父进程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 函数获取当前父进程IDecho "父进程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 这个函数可以很优雅的解决了孤儿进程和僵尸进程,但在实际的编程中很容易忽视这一点,因此这一点值得注意。本次分享的内容就到这里了,希望对大家能有所帮助。

文章转载自:Yxh_blogs

原文链接:https://www.cnblogs.com/yxhblogs/p/18337236

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

这篇关于聊聊 PHP 多进程模式下的孤儿进程和僵尸进程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1099891

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

Linux中的进程间通信之匿名管道解读

《Linux中的进程间通信之匿名管道解读》:本文主要介绍Linux中的进程间通信之匿名管道解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基本概念二、管道1、温故知新2、实现方式3、匿名管道(一)管道中的四种情况(二)管道的特性总结一、基本概念我们知道多

Linux进程终止的N种方式详解

《Linux进程终止的N种方式详解》进程终止是操作系统中,进程的一个重要阶段,他标志着进程生命周期的结束,下面小编为大家整理了一些常见的Linux进程终止方式,大家可以根据需求选择... 目录前言一、进程终止的概念二、进程终止的场景三、进程终止的实现3.1 程序退出码3.2 运行完毕结果正常3.3 运行完毕

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Windows命令之tasklist命令用法详解(Windows查看进程)

《Windows命令之tasklist命令用法详解(Windows查看进程)》tasklist命令显示本地计算机或远程计算机上当前正在运行的进程列表,命令结合筛选器一起使用,可以按照我们的需求进行过滤... 目录命令帮助1、基本使用2、执行原理2.1、tasklist命令无法使用3、筛选器3.1、根据PID

linux本机进程间通信之UDS详解

《linux本机进程间通信之UDS详解》文章介绍了Unix域套接字(UDS)的使用方法,这是一种在同一台主机上不同进程间通信的方式,UDS支持三种套接字类型:SOCK_STREAM、SOCK_DGRA... 目录基础概念本机进程间通信socket实现AF_INET数据收发示意图AF_Unix数据收发流程图A

Python中多线程和多进程的基本用法详解

《Python中多线程和多进程的基本用法详解》这篇文章介绍了Python中多线程和多进程的相关知识,包括并发编程的优势,多线程和多进程的概念、适用场景、示例代码,线程池和进程池的使用,以及如何选择合适... 目录引言一、并发编程的主要优势二、python的多线程(Threading)1. 什么是多线程?2.

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.