python中的守护进程、僵尸进程、孤儿进程

news2024/11/26 9:31:24

继续上一篇文章的探讨:https://blog.csdn.net/weixin_39743356/article/details/137885419

守护进程

守护进程(Daemon Process)是一种在后台运行的特殊类型的进程,它独立于控制终端,并且周期性地执行某种任务或等待处理某些事件。在Unix-like系统中,守护进程通常在系统启动时启动,并持续运行直到系统关闭。由于它们不与任何用户交互,因此被认为是“服务”型的进程,通常用于执行系统任务,如日志记录、网络服务、系统监控等。

守护进程的特点包括:

  1. 通常在系统启动时自动运行。
  2. 在后台运行,不与任何终端会话相关联。
  3. 通常具有较高的权限,以便能够执行一些需要特权的操作。
  4. 经常被设计为长时间运行的服务,而不是执行一次性任务。
  5. 通常没有用户界面,而是通过命令行或配置文件进行交互和配置。
  6. 在系统关闭时通常会被优雅地终止,以确保数据的完整性。

步骤

  1. 从命令行或终端会话中分离出来,使得守护进程不依赖于任何终端。
  2. 改变工作目录,通常是到/或者创建一个专用的目录。
  3. 改变文件创建掩码(umask),以确保新创建的文件对其他用户是可读的。
  4. 关闭所有非必需的文件描述符,以避免文件泄露。
  5. 调用daemon()函数(如果使用C语言)或者使用Python的subprocess模块中的相关功能来将进程转变为守护进程。
  6. 启动主服务循环,执行守护进程的主要职责。

os.fork()示例

Python中创建守护进程可以通过os.fork()方法来实现,在子进程中再次使用os.fork()确保该子进程不会成为会话领导(session leader),从而避免获取控制终端。同时设置新的工作目录、文件权限掩码和关闭所有打开的文件描述符来与控制终端彻底脱离关系。

import os
import time
from multiprocessing import current_process


def daemonize():
    # 第一次fork,生成子进程,脱离父进程
    try:
        if os.fork() > 0:
            raise SystemExit(0)  # 父进程退出
    except OSError as e:
        raise RuntimeError('fork #1 failed.')

    print("子进程执行了", current_process().name, current_process().pid)
    os.chdir('/')  # 修改工作目录
    os.umask(0)  # 重新设置文件创建权限
    os.setsid()  # 设置新的会话连接

    # 第二次fork,防止子程序获取控制终端。
    try:
        if os.fork() > 0:
            raise SystemExit(0)
    except OSError as e:
        raise RuntimeError('fork #2 failed.')

    # 关闭打开的文件描述符。
    print("子进程执行了", current_process().name, current_process().pid)
    with open('/dev/null', 'r+b') as f_null:
        for fd in range(3):  # STDIN, STDOUT, STDERR.
            try:
                os.dup2(f_null.fileno(), fd)
            except OSError as e:
                pass


def main():
    while True:
        print("Daemon process running.")
        time.sleep(5)


if __name__ == '__main__':
    print("父进程执行了", current_process().name, current_process().pid)
    daemonize()
    main()
  1. 程序开始执行,并且当前只有一个进程,即主进程。
  2. 主进程调用 os.fork() 创建了一个子进程。这时候存在两个几乎完全相同的进程:父(主)进程和子进程。
  3. 在父(主)程序中,os.fork() 返回新创建子程序的PID(大于0),然后父程序通过调用 exit(0) 退出。这时候父程序结束了自己的生命周期。
  4. 在子程序中,os.fork() 返回0,表示这是在子程序内部执行。
    1. 子进程首先会打印自己的名称和PID,然后执行一系列操作来确保它成为一个守护进程。
    2. os.chdir('/') 改变子进程的工作目录到根目录/
    3. os.umask(0) 设置文件创建掩码为0,这样新创建的文件对其他用户是可读的。
    4. os.setsid() 创建一个新的会话ID(SID),这个操作确保子进程不会成为任何终端的会话领导者,从而避免了控制终端的影响。
  5. 第二次 fork() 调用后:
    • 子程序再次成为父亲并创建了另一个新的子程序。
    • 新创建出来的子进程会继续运行代码剩余部分。
    • 第一次被创建出来的进程则退出。因为第一次被创建出来的进程已经完成了其使命:确保最终运行代码剩余部分的那个子进程不会成为会话领导者(session leader),从而避免获得控制终端。
  6. 最后的子进程将标准输入、标准输出和标准错误(即文件描述符0、1和2)重定向到了 /dev/null,因此main方法打印的内容不会显示在任何控制台或终端上。这是守护进程常见的做法,因为它们通常不应该与用户交互或产生输出到控制台。确保了守护进程不会占用任何终端资源,也不会产生僵尸进程。
  7. 这个最后的进程就是我们想要实现功能逻辑上真正意义上“守护”的过程;它没有控制终端、独立于其他用户交互式过场景,并且可以持续在背景里面执行任务直至系统关闭或者任务完成自我结束。

输出:

父进程执行了 MainProcess 75046
子进程执行了 MainProcess 75047
子进程执行了 MainProcess 75048

执行以下命令可以看到子进程还在后台

ps -p 75046,75047,75048 -o pid,ppid,comm,state,user,vsz,command

请添加图片描述

记得手动kill掉

kill -9 75048

补充

os.dup2(f_null.fileno(), fd) 这行代码是在进行文件描述符的重定向操作。在Unix和类Unix系统中,每个进程都有一组文件描述符,它们是非负整数,用于引用打开的文件、管道或网络连接。其中,标准输入(STDIN)、标准输出(STDOUT)和标准错误(STDERR)分别被分配了固定的文件描述符编号:0、1 和 2。

具体来说:

  • f_null.fileno() 返回 /dev/null 文件对象 f_null 的文件描述符。
  • fd 是要被替换的目标文件描述符编号,在这里指的是 0、1 、 2。

调用 os.dup2(x, y) 将会:

  1. 关闭当前进程中编号为 y 的文件描述符(如果它已经打开)。
  2. 复制编号为 x 的文件描述符,并将其复制到编号为 y 的位置上。

因此,在这种情况下,该操作将关闭子进程中原本与标准输入、输出和错误相关联的文件描述符,并将它们全部重定向到 /dev/null。这意味着任何尝试从STDIN读取数据的操作都会立即返回EOF(表示没有数据可读),任何尝试写入STDOUT或STDERR的输出都会被丢弃并不会显示在任何地方。

这样做通常是出于以下原因:

  • 防止守护进程产生任何终端I/O操作,因为守护进程应该独立于控制终端运行。
  • 避免由于未处理输出导致资源泄露或其他潜在问题。
  • 确保即使程序尝试读取输入或写入日志信息也不会对程序执行产生影响。

简而言之,通过重定向到 /dev/null, 守护进程可以无声无息地运行其任务而不干扰其他系统活动。

multiprocessing示例

multiprocessing中,每个Process对象都有一个属性叫做daemon,当这个属性被设置为True时,这个进程就会成为守护进程。守护进程在其父(主)进程终止时会自动终止,并且通常不用于执行需要长时间运行的任务。

import multiprocessing
import time


def daemon_process():
    print('Starting my daemon process')
    while True:
        time.sleep(1)
        print('Daemon process is running...')


if __name__ == '__main__':
    # 创建一个守护子程序
    d = multiprocessing.Process(name='Daemon', target=daemon_process)
    d.daemon = True  # 设置为True表示这是一个守护程序

    # 启动守护子程序
    d.start()

    # 主程序将等待一段时间然后退出
    print('Main process is running...')
    time.sleep(5)
    print('Main process is exiting.')

  • 定义了一个名为 daemon_process() 的函数作为我们想要以守护方式运行的任务。
  • if __name__ == '__main__': 块中,我们创建了 Process() 对象,并将其 daemon 属性设置为True。
  • 然后启动该子程序并让主程序休眠5秒钟。
  • 当主程序完成休眠并退出时,由于子程序是以守护方式运行的,所以也会自动停止。

os.fork实现的例子中为什么在pycharm中跑完了进程还在,而用multiprocessing实现的确直接结束了?

  1. 使用os.fork() 的情况下:
    • PyCharm可能没有检测到父进程退出后子进程仍然在运行。因此,在父进程结束执行后,IDE界面可能显示程序已经“完成”,但实际上孤立的子进程(守护进程)仍然在系统中运行。
    • 这个孤立的子程序并没有被PyCharm所管理,所以即使IDE认为程序已经结束了,实际上该子程序还是会持续运行直至自身结束或者被外部强制杀死。
  2. 使用 multiprocessing 的情况下:
    • 当主程序创建一个设置了 daemon=True 的多处理子程序时,并且主程序退出后,默认情况下所有守护式多处理子程序也会自动退出。
    • PyCharm能够正确地管理和追踪通过 multiprocessing 模块创建的所有多处理任务,并且当主任务结束时也能够确保所有守护式多处理任务都被正确地关闭。

僵尸进程

在操作系统中,僵尸进程(Zombie Process)是指已经完成执行(终止)但仍然有一个记录存在进程表中的进程。这个记录包含了进程的一些信息,如退出状态、运行时间等,以便父进程查询。僵尸进程本身不占用任何系统资源,除了在进程表中的一个位置。

在Unix和类Unix系统(比如Linux)中,当一个子进程结束运行时,并不会立即从系统中完全清除。如果父进程还在运行,它需要通过调用wait()waitpid()函数来读取子进程的退出状态。在父进程读取了子程序的结束状态之前,子程序会保留为僵尸状态。

wait()waitpid() 是 Unix 系统调用,它们被用于父进程中以等待和回收子进程的资源,防止子进程成为僵尸进程。

  1. wait():
    • wait() 系统调用使得一个父进程暂停执行,直到它的一个子进程结束或者该父进程接收到一个指定的信号。
    • 当子进程结束时,wait() 会回收子进程所占用的资源,并清除系统中该子进程的记录。
    • 如果有多个子进程,则 wait() 会等待任一子进程结束,并返回终止了的那个子进程的 PID。
  2. waitpid():
    • waitpid() 是更灵活版本的 wait()。它允许父进程指定要等待哪个具体的子进rocess 或者是某一类特定状态变化(如停止或终止)。
    • 它有几个参数:第一个参数是你想要等待状态改变的特定 PID(如果传入 -1 则与 wait() 相同,表示任何一个),第二个参数是存储状态信息(通常是退出码) 的地址,第三个参数可以设置为不同值来修改函数行为(例如是否立即返回而不阻塞)。
    • 使用这种方式可以实现更精确地控制对哪些子进行管理和如何管理。

在 Python 中使用多处理模块时,默认情况下并不需要直接调用这些系统调用。Python 的 multiprocessing 库提供了自己高层次、跨平台版本的 API 来处理相关问题。例如,在 Python 中使用 Process.join() 方法就能够达到类似于 wait/waitpid 的效果——等待子进程结束并回收其资源。

如果父进程没有调用wait()waitpid()来获取子程序的状态信息,则该僵尸程序将一直存在。如果其父程序先于它终止,则该僵尸程序将被init(PID为1)接管,并由init来负责调用wait()回收资源。

模拟僵尸进程代码:

import multiprocessing
import time
from multiprocessing import current_process


def func():
    print("子进程执行了", current_process().name, current_process().pid)
    exit()


if __name__ == '__main__':
    print("父进程执行了", current_process().name, current_process().pid)
    process = multiprocessing.Process(target=func)
    process.start()  # 创建进程
    time.sleep(300)

代码仅仅只是延迟主进程回收子进程资源的时间而已,而这个时间段内对于操作系统而言,就会认为该子进程是僵尸进程。但是并不会造成真正的僵尸进程的出现。因为主进程结束以后还是会回收子进程的数据的。

Python解释器内部实现了对于进程回收的操作进行高度封装和安全处理,所以python中我们不需要担心僵尸进程的出现。

输出

父进程执行了 MainProcess 59423
子进程执行了 Process-1 59425

在mac系统中查看进程状态:

ps -p 59423,59425 -o pid,ppid,comm,state,user,vsz,command

“R”表示运行中,“S”表示睡眠状态,“Z”表示僵尸进程等。

请添加图片描述

在Windows操作系统中,通常不会出现类似于Unix或Linux系统中的僵尸进程(Zombie Process)。

Windows操作系统采用了不同的机制来处理已经结束运行但未被完全清理的进程。当一个Windows程序完成执行后,它的状态和资源通常由操作系统自动清理。如果父进程没有等待子进程(也就是没有调用WaitForSingleObject或类似函数),那么当子进程结束时,它所占用的所有资源都会被立即释放,并且该过程对用户是透明的。

孤儿进程

孤儿进程(Orphan Process)是指在Unix-like系统中,父进程在其子进程结束之前退出或终止了,而这些子进程还在运行的情况。当父进程终止后,所有未终止的子进程将被init进程(PID为1的特殊系统进程)接管。init进程会自动成为这些孤儿子进程的新父亲,并负责对它们执行wait()调用来回收它们结束时留下的资源和状态信息。

孤儿进程通常不会造成系统资源的浪费,因为它们仍然在执行其任务,只是没有了原来的父进程。当这些进程结束执行后,init进程将确保它们被正确清理,防止它们变成僵尸进程。孤儿进程的存在通常不会影响系统性能,除非有大量未被合理管理的孤儿进程积累。在某些情况下,孤儿进程的数量过多可能会导致系统资源压力。

代码模拟孤儿进程:

import os
import time
from multiprocessing import Process


def func():
    print(f"子进程的pid={os.getpid()}")
    time.sleep(60)
    print("hello")

if __name__ == '__main__':
    print(f"主进程的pid={os.getpid()}")
    p = Process(target=func)
    p.daemon = True
    p.start()
    time.sleep(15)

输出

主进程的pid=62467
子进程的pid=62469

macOS下:

1、执行命令:

ps -p 62467,62469 -o pid,ppid,comm,state,user,vsz,command

输出:

请添加图片描述

2、执行命令kill掉主进程,填入主进程id

kill -9 62467

3、再次查看就可以发现,子进程变成了一个还在运行的孤儿进程,并且父进程编程了init(ppid为1)

ps -p 62467,62469 -o pid,ppid,comm,state,user,vsz,command

请添加图片描述

4、等孤儿进程结束后,会被init回收,再次执行命令可以发现已经没有了

请添加图片描述

同样在Windows操作系统中,进程模型与Unix-like系统不同,因此孤儿进程的概念并不完全适用。在Windows中,当一个父进程退出时,其子进程并不会成为孤儿进程。相反,子进程继续运行,并且它们的生命周期与父进程的结束是独立的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

性能分析与调优

性能分析方法 自底向上:通过监控硬件及操作系统性能指标(cpu、内存、磁盘、网络等硬件资源的性能指标)来分析性能问题(配置、程序问题) 先检查,再下药 自顶向下:通过生成负载来观察被测试的系…

解锁外贸财务系统宝藏:多语言多货币平台推荐汇总

本文将为您推荐几款主流的多语言多货币外贸财务系统有:Zoho Books 、SAP、Oracle、QuickBooks、Xero、TradeGecko,并分析了每款产品的主要特点,希望能帮助到你! 一、Zoho Books Zoho Books是一款支持180种货币、17种语言的外贸…

视频技术笔记-色差分量

色差分量接口采用YPbPr和YCbCr两种标识。 YPbPr:表示逐行扫描色差输出。 YCbCr:后者表示隔行扫描色差输出。 色差分量接口一般利用3根信号线分别传送亮色和两路色差信号。 色差分量接口是色差接口使用不是很普遍,主要的原因是一些CRT电视机…

CSS 格式化上下文 + CSS兼容处理

个人主页:学习前端的小z 个人专栏:HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论! 文章目录 ✍CSS 格式化上下文🔥1 格式化上下文🌷1.1 块级格式化…

BIM数据管理快速指南

在我的日常工作中,作为数字协作协调员,我花费大量时间收集、检查和管理各种 BIM 数据。 很多次收到一组数据后我就无奈地举手——质量远远达不到我可以使用的程度。 然后我会开始一个普通的数据清理过程。 我无数次咒骂过这种情况——大多数建设项目的人…

vue动态添加style的样式

vue在动态添加style样式的时候,有以下注意点 1.凡是有-的style属性名都要变成驼峰式,比如font-weight,需要写成fontWeight 2.除了绑定值,其他属性名的值要用引号括起来,比如width:‘75px’,不要忘记引号 3.动态绑定时&…

ipv4Bypass:一款基于IPv6实现的IPv4安全绕过与渗透测试工具

关于ipv4Bypass ipv4Bypass是一款基于IPv6实现的安全绕过与渗透测试工具,该工具专为红队研究人员设计,可以帮助广大研究人员通过IPv6绕过目标安全策略,以此来检测安全检测机制的健壮性。 20世纪90年代是互联网爆炸性发展时期,随着…

2024华中杯C题完整解题思路及代码

C 题 基于光纤传感器的平面曲线重建算法建模 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技 术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号,其 基本原理是当外界环境参数发生变化时,会引起光纤传感器中光波参量&…

python环境引用《解读》----- 环境隔离

首先我先讲一下Anaconda,因为我用的是Anaconda进行包管理。方便后面好理解一点。 大家在python中引用环境的时候都会经历下面这一步: 那么好多人就会出现以下问题(我就是遇到了这个问题): 我明明下载了包&#xff0c…

oracle 数据库 迁移 mysql

将 Oracle 数据库迁移到 MySQL 是一项复杂的任务,因为这两种数据库管理系统具有不同的架构、语法和功能。下面是一个基本的迁移步骤,供你参考: 步骤一:评估和准备工作 1.评估数据库结构:仔细分析 Oracle 数据库的结构…

Python实战:批量加密Excel文件,保护数据安全!

在日常工作中,我们经常需要处理大量的Excel文件。 为了保护敏感数据的安全性,我们可能需要对这些文件进行加密。 本文将介绍如何使用Python实现批量加密Excel文件的操作,以提高工作效率和数据安全性。 安装所需的库 在开始之前&#xff0…

Java 网络编程之TCP:基于BIO

环境: jdk 17 IntelliJ IDEA 2023.1.1 (Ultimate Edition) Windows 10 专业版 22H2 TCP:面向连接的,可靠的数据传送协议 Java中的TCP网络编程,其实就是基于常用的BIO和NIO来实现的,本文先讨论BIO; BIO…

润开鸿与蚂蚁数科达成战略合作,发布基于鸿蒙的mPaaS移动应用开发产品

4月18日,江苏润和软件股份有限公司(以下简称“润和软件”) 旗下专注鸿蒙方向的专业技术公司及终端操作系统发行版厂商江苏润开鸿数字科技有限公司(以下简称“润开鸿”)与蚂蚁数科举行战略合作签约仪式,并发…

Qt 拖放功能详解:理论与实践并举的深度指南

拖放(Drag and Drop)作为一种直观且高效的用户交互方式,在现代图形用户界面中扮演着重要角色。Qt 框架提供了完善的拖放支持,允许开发者在应用程序中轻松实现这一功能。本篇博文将详细阐述Qt拖放机制的工作原理,结合详…

HTTP请求中的cookie与session(servlet实现登录页面的表单验证)

一、cookie 与 session 1&#xff09;cookie 与 session 的定义 2&#xff09;相关的servlet中的 方法 二、代码实现 登录页面 1&#xff09;先用 vscode 编写登录页面 注意文件的路径 在webapp路径下 <!DOCTYPE html> <html lang"en"><head>&…

03节-51单片机-独立按键模块

1. 独立按键控制LED状态 轻触按键实现原理&#xff1a;按下时&#xff0c;接通&#xff0c;通过金属弹片受力弹动来实现接通和断开。 松开按键 按下之后&#xff1a;就会被连接 同时按下K1和K2时&#xff0c;P2_0,接口所连LED灯才亮。 #include <REGX52.H> void ma…

PCA人脸识别

目录 一、PCA主成分分析 二、PCA人脸识别 三、结果 一、PCA主成分分析 PCA&#xff08;主成分分析&#xff09;是一种非常常用的数据降维技术。它通过线性变换将原始数据变换到一个新的坐标系统中&#xff0c;使得在这个新坐标系统的第一个坐标轴上的数据方差最大&#xff…

Python | Leetcode Python题解之第36题有效的数独

题目&#xff1a; 题解&#xff1a; class Solution:def isValidSudoku(self, board: List[List[str]]) -> bool:uni (defaultdict(set), defaultdict(set), defaultdict(set))for i in range(9):for j in range(9):s board[i][j]if s .: continueif s in uni[0][i] or …

uniapp:小白1分钟学会使用webSocket(可无脑复制)

uni.connectSocket() uni.$emit页面通信 项目中使用uni.connectSocket()创建webSocket的总结&#xff0c;代码可无脑复制&#xff0c;直接使用。 1、main.js 引入vuex import store from ./store; Vue.prototype.$store store;vuex中封装webSocket 2、vuex的&#xff1a;index…

SpringBoot整合PDF动态填充数据并下载

目录 目录 一、准备环境 二、iTextPDF介绍 三、步骤 四、访问查看结果 五、源代码参考 一、准备环境 ①下载一个万兴pdf软件 ②准备一个pdf 文件 二、iTextPDF介绍 这是一个用于生成PDF文档的Java库&#xff0c; 文档创建与修改&#xff1a;iTextPDF能够从零开始创建…