从一道面试题来学习前台进程和后台进程、孤儿进程和僵尸进程

1、面试题介绍

以前面试,面试官问了一个问题,大意是:

我们在终端中,通过执行 python main.py 命令,会启动一台前台进程直到程序结束。现在我还是想通过执行 python main.py ,启动一个后台进程,让后台进程运行我们的业务逻辑。这个时候应该怎么做呢?

回答上面这道题,需要先了解什么是前台进程和后台进程,什么是孤儿进程和僵尸进程?接下来,我们先一起看看前台进程和后台进程,以及孤儿进程和僵尸进程。最后再通过编写代码来完成面试题的需求。

2、前台进程和后台进程

2.1 什么是前台进程

在 Linux 中,前台进程是指当前正在运行的进程,它与用户交互并占用终端。当用户在终端中输入命令时,该命令所启动的进程就是前台进程。

前台进程会占用终端,直到它执行完毕或者被中断(例如按下 Ctrl+C)。在前台进程运行期间,用户可以通过键盘输入命令或者发送信号来与进程交互。

2.2 什么是后台进程

Linux 后台进程是指在终端中运行的进程,但是不会占用终端的输入输出,而是在后台运行

在 Linux 中,可以通过在命令后面加上 & 符号来将进程放到后台运行。例如,运行命令 command & 就可以将 command 进程放到后台运行。

后台进程可以在终端关闭后继续运行,也可以同时运行多个后台进程。可以使用 jobs 命令查看当前运行的后台进程,使用 fg 命令将后台进程切换到前台运行,使用 bg 命令将前台进程切换到后台运行。

需要注意的是,后台进程在另外的终端是查看不到任何信息的,如果需要查看进程的输出信息,一般是将输出重定向到文件中,然后使用 tail 命令实时查看输出。

2.3 前台进程、后台进程如何切换

在 Linux 中,可以使用以下命令将前台进程切换到后台进程:

  1. 使用 Ctrl + Z 将当前正在运行的前台进程挂起。
  2. 使用 bg 命令将挂起的进程切换到后台运行。

使用 command & ,这样的方式是一开始就将 command 进程放到后台运行。

我们通过下面这个例子,来感受下前台进程和后台进程是如何切换的。

1、编写 test.py文件。

import os
import time


def main():
    a = 1
    while True:
        time.sleep(1)
        a += 1
        print("a---->", a)
        if a > 30:
            break


if __name__ == '__main__':
    main()

2、通过 python test.py执行程序。然后使用ctrl + Z 是程序暂停到后台。注意,这个时候程序不会再往下执行了。

  • 如果想程序继续往下执行,使用 bg 命令将其切换到后台运行。

  • 还有一种方式是:在最开始执行命令的时候,加上 &。即python main.py &

python test.py
a----> 2
a----> 3
^Z
[1]  + 1761 suspended  python test.py
sample_test [master●] %

3、通过jobs命令查询后台进程。

sample_test [master●] % jobs          
[1]  + suspended  python test.py
sample_test [master●] %

4、使用fg命令将指定的后台进程切换到前台运行。(fg %N,这里的N是 jobs返回的序号,并不是进程的PID)

    
sample_test [master●] % fg %1     
[1]  + 1761 continued  python test.py
a----> 4
a----> 5
a----> 6
a----> 7

3、孤儿进程和僵尸进程

通过上面的讲解,我们知道了什么是后台进程和前台进程。接下来,我们再讲讲什么是孤儿进程和僵尸进程。

3.1 什么是孤儿进程

孤儿进程是指父进程已经结束或者异常退出,而子进程还在运行的情况下,子进程就会变成孤儿进程。孤儿进程会被操作系统的init进程接管init进程会成为孤儿进程的新的父进程,并负责回收孤儿进程的资源,避免资源泄露和浪费。

因此一般来说,孤儿进程并不会有什么危害。

我们来看一个关于孤儿进程的例子:

在main函数中,创建子进程,然后让父进程睡眠1s,让子进程先运行打印出其进程id(pid)以及父进程id(ppid);随后子进程睡眠3s(此时会调度到父进程运行直至结束),目的是让父进程先于子进程结束,让子进程有个孤儿的状态;最后子进程再打印出其进程id(pid)以及父进程id(ppid);观察两次打印 其父进程id(ppid)的区别。

import os
import time


def main():
    pid = os.fork()  # 创建子进程
    if pid < 0:
        # 创建失败
        print("fork failed!")
        exit(1)
    elif pid == 0:  # 子进程
        print("I am the child process.")
        print("pid:%d, ppid:%d " % (os.getpid(), os.getppid()))
        # 子进程睡眠3s,保证父进程先退出,此后子进程成为孤儿进程
        time.sleep(3)
        # 注意查看孤儿进程的父进程变化
        print("after sleep, pid:%d, ppid:%d" % (os.getpid(), os.getppid()))
        assert os.getppid() == 1
        print("child process is exited.")
    else:
        print("I am father process.")
        # 为保证子进程先运行,让父进程睡眠1s
        time.sleep(1)
        print("father process is exited.")




if __name__ == '__main__':
    main()

执行结果:

从一道面试题来学习前台进程和后台进程、孤儿进程和僵尸进程

运行结果表明:当父进程结束后,子进程成为了孤儿进程。因为它的父进程id(ppid)变成了1,即init进程成为该子进程的父进程了。

3.2 什么是僵尸进程

Linux 僵尸进程是指已经结束执行的进程,但是其父进程还没有对其进行处理,导致该进程的进程描述符仍然存在于系统中,这种进程被称为僵尸进程

僵尸进程不会占用系统资源,但是如果大量的僵尸进程存在,会占用系统的进程描述符资源,导致系统进程描述符资源耗尽,从而导致系统崩溃。

为了避免僵尸进程的出现,父进程需要及时对其进行处理,可以使用 wait() 或 waitpid() 等系统调用来等待子进程结束并回收其资源。另外,也可以使用信号处理机制,在父进程中注册 SIGCHLD 信号处理函数来处理子进程结束的信号。

关于僵尸进程的例子

在main函数中,创建子进程,然后让父进程睡眠30s,让子进程先终止(注意和孤儿进程例子的区别);这里子进程结束后父进程没有调用wait/waitpid函数获取其状态,用ps查看进程状态可以看出子进程为僵尸状态。

import os
import time


def main():
    pid = os.fork()  # 创建子进程
    if pid < 0:
        # 创建失败
        print("fork failed!")
        exit(1)
    elif pid == 0:  # 子进程
        print("I am the child process.I am exited.")
        exit(0)
    else:
        print("I am father process.")
        #  父进程睡眠30s等待子进程退出,且没有调用wait/waitpid获取其状态
        #  子进程会成为僵尸进程
        time.sleep(30)
        print("father process is exited.")




if __name__ == '__main__':
    main()

开一个终端,在终端是运行test.py

从一道面试题来学习前台进程和后台进程、孤儿进程和僵尸进程

在子进程结束,父进程睡眠(还没退出)的时候,再开一个终端用PS查看进程状态。

从一道面试题来学习前台进程和后台进程、孤儿进程和僵尸进程

注意:

  1. 任何一个子进程(init除外)在exit()之后,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。
  2. 如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
  3. 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
  4. 如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是系统中有时候会有很多僵尸进程的原因。

那么如何杀死僵尸进程呢,可以查询 僵尸进程与孤儿进程 链接,来进行学习,这里就不再讲述。

3.3 总结

  • 孤儿进程:父进程已亡,子进程成为孤儿,被init进程收养,由init进程对它们完成状态收集工作,因此一般没有坏的影响。
  • 僵尸进程:子进程已亡,父进程没给子进程收尸,子进程成为僵尸进程,占用系统资源。

4、面试题解决方式

现在再回过头来看 面试题的要求:

在终端中执行 python main.py命令,启动后台进程来进行业务处理。

那么我们可以利用孤儿进程的特性,完成上面的需求。

1、通过 os.fork()创建子进程。

2、创建完成后,让父进程退出,子进程继续运行。

简单案例:

import os
import time


def main():
    pid = os.fork()  # 创建子进程
    a = 0
    if pid < 0:
        # 创建失败
        print("fork failed!")
        exit(1)
    elif pid == 0:  # 子进程
        for i in range(10):
            time.sleep(1)
            a += i
        print("child process calculate result: %d" % a)
    else:
        print("I am father process.")
        exit(0)


if __name__ == '__main__':
    main()
sample_test [master●] % python test.py
I am father process.


10秒钟过后
sample_test [master●] % child process calculate result: 45

参考资料:

僵尸进程与孤儿进程

原文链接:https://www.cnblogs.com/huageyiyangdewo/p/17325168.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:从一道面试题来学习前台进程和后台进程、孤儿进程和僵尸进程 - Python技术站

(0)
上一篇 2023年4月16日
下一篇 2023年4月17日

相关文章

  • python统计字符的个数代码实例

    当我们在处理文本数据时,需要统计字符串中每个字符出现的次数。在Python中,我们可以使用字典(dict)来实现这个功能。以下是实现“Python统计字符的个数”代码的步骤: 步骤一:定义函数 我们可以定义一个函数,传入文本字符串作为参数,在函数内部统计每个字符的个数并将结果存储在字典(dict)中。 def count_characters(text): …

    python 2023年6月5日
    00
  • Python any()和all()进行规约

    以下是详细讲解Python any()和all()函数的使用方法: 概述 在Python中,any()和all()是两个内置函数,用于判断可迭代对象中的元素是否符合规约条件。它们通常与条件表达式和Lambda表达式一起使用,能够极大地方便代码的编写和阅读。下面我们分别介绍它们的用法。 any() any()函数接受一个可迭代对象作为参数(如列表、元组、集合、…

    python-answer 2023年3月25日
    00
  • python-xpath获取html文档的部分内容

    Python-XPath获取HTML文档的部分内容 在本文中,我们将介绍如何使用Python和XPath从HTML文档中获取部分内容。XPath是一种用于在XML和HTML文档中选择元素的语言。我们将提供两个示例,以帮助读者更好地理解如何实现这个目标。 步骤1:安装必要的库 在使用Python和XPath获取HTML文档的部分内容之前,我们需要安装必要的库。…

    python 2023年5月15日
    00
  • python基于watchdog库全自动化监控目录文件

    下面是详细讲解”python基于watchdog库全自动化监控目录文件”的完整攻略。 什么是watchdog库 watchdog库是一个Python库,它可以监视文件系统中的任何目录变化,并根据您的需要执行操作。它支持文件和目录的监控,并通过轮询文件系统来检查更改。可以通过在此基础上构建项目来轻松实现全自动化监控。 安装watchdog库 请先确保已按照所需…

    python 2023年6月5日
    00
  • python GUI库图形界面开发之PyQt5切换按钮控件QPushButton详细使用方法与实例

    Python GUI库图形界面开发之PyQt5切换按钮控件QPushButton详细使用方法与实例攻略 一、控件简介 PyQt5的QPushButton控件是一种切换按钮控件。它通常被用于创建带有切换功能的用户界面。当用户单击该按钮时,该按钮的状态会改变。例如,我们可以将该按钮用于激活或禁用一个应用程序的组件。 二、控件用法 1. 创建QPushButton…

    python 2023年6月13日
    00
  • python中scipy.stats产生随机数实例讲解

    下面是“Python中scipy.stats产生随机数实例讲解”的完整攻略: 什么是SciPy? SciPy是一个用于科学计算的Python库。 它主要用于数值计算,包括求解线性代数、 优化、 随机采样和信号处理等问题。 什么是SciPy.stats? Scipy.stats是Scipy中的概率分布函数库,主要用于生成和模拟各种概率分布,如正态分布、卡方分布…

    python 2023年6月3日
    00
  • Python学习之自定义异常详解

    以下是关于Python学习之自定义异常详解的完整攻略: 问题描述 在Python中,我们可以自定义异常来处理特定的错误或异常情况。自定义异常可以让我们更好地控制程序的流程和错误处理。但是,自定义异常需要遵循一定的规则和约定。 解决方法 可以使用以下步骤自定义异常: 定义异常类。 定义一个继承自Exception类的异常类。可以在异常类中添加一些属性或方法,以…

    python 2023年5月13日
    00
  • Python Numpy中的Kaiser

    Kaiser是一种数字信号处理中常用的窗函数,它在时域上具有抗旁瓣能力,因此被广泛地用于滤波器设计和频谱分析。Python中的NumPy库提供了丰富的函数和工具来支持快速的Kaiser窗设计和应用。 Kaiser 窗函数简介 Kaiser窗函数常常被用来设计数字滤波器,它的主要特点是在频域上具有宽带过渡区和优良的波形抗干扰特性,同时具有指定截止频率处盈余峰值…

    python-answer 2023年3月25日
    00
合作推广
合作推广
分享本页
返回顶部