吊打面试官之 - python多进程信号接收与处理


前言

面试的时候经常会被问到process信号处理的一些细节,还有process启动的一些细节,如何吊打面试官,让他不敢还嘴呢? 我们可以通过langchain-chatchat 的代码来详细了解信号处理。

代码是startup.py 的 async def start_main_server(): 方法,大家可以先去看一下。我们下面的例子是对它的一个改编

一、示例代码

import os
import time
from multiprocessing import Process
from time import sleep

import signal


def handler(signalname):

    def f(signal_received, frame):
        raise KeyboardInterrupt(f"{signalname} received")

    return f


def signal_handler(sig, frame):
    print(f"Received signal {sig} in process {os.getpid()}")
    # 在这里执行自定义的清理操作
    # 例如关闭文件、释放资源等
    # 这个示例中,我们简单地退出程序
    # sleep(10)
    exit(0)


def task():
    # 打开文件
    file_handle = open("example.txt", "w")

    def task_signal_handler(sig, frame):
        print("Ctrl+C pressed. Exiting gracefully...")
        # 在这里关闭打开的文件
        if file_handle:
            print("Closing file...")
            file_handle.close()
        exit(0)

    signal.signal(signal.SIGINT, task_signal_handler)
    # 打印当前进程id
    print("pid:", os.getpid())
    print("task")

    sleep(10)
    # 打印当前时分秒
    time_str = time.strftime("%H:%M:%S", time.localtime())
    print(time_str)


if __name__ == "__main__":
    # 注册信号处理函数
    # signal.signal(signal.SIGINT, signal_handler)
    print("Press Ctrl+C to exit gracefully.")
    # 打印当前进程id
    print("pid:", os.getpid())
    signal.signal(signal.SIGINT, handler("SIGINT"))
    signal.signal(signal.SIGTERM, handler("SIGTERM"))
    p1 = Process(target=task, daemon=True)
    try:
        p1.start()
        print("执行下一个任务")
        p1.join()
        print("执行完毕")
    except KeyboardInterrupt as e:
        print(e)
    finally:
        print("Cleaning up...")
        # p1.kill()
        p1.terminate()
        p1.join()
        print(f"Process status: {p1.is_alive()}")

1. 注册信号

大家可以看到这里有2种写法

signal.signal(signal.SIGINT, task_signal_handler)

signal.signal(signal.SIGINT, handler("SIGINT"))
实际上,注册信号的时候,是不能给后面的方法增加自定义参数的。那么为什么第二种可以增加一个自定义参数并传值SIGINT呢?

2.信号处理函数

1)普通写法

def signal_handler(sig, frame):
    print(f"Received signal {sig} in process {os.getpid()}")
    # 在这里执行自定义的清理操作
    # 例如关闭文件、释放资源等
    # 这个示例中,我们简单地退出程序
    # sleep(10)
    exit(0)

2)闭包写法

def handler(signalname):

    def f(signal_received, frame):
        raise KeyboardInterrupt(f"{signalname} received")

    return f

闭包方法实际上是把一个f(signal_received, frame)作为函数传递出去,也就是在
signal.signal(signal.SIGINT, handler("SIGINT")) 中最终第二个参数等同于信号注册函数需要的函数格式。这样就巧妙的利用闭包增加了一个自定义参数

3. 对终止信号的重写

1)终止前关闭资源

def task_signal_handler(sig, frame):
        print("Ctrl+C pressed. Exiting gracefully...")
        # 在这里关闭打开的文件
        if file_handle:
            print("Closing file...")
            file_handle.close()
        exit(0)

2)不关闭只抛出异常

def handler(signalname):

    def f(signal_received, frame):
        raise KeyboardInterrupt(f"{signalname} received")

    return f

这样可以让别的地方接收异常,然后再处理例如

try:
        p1.start()
        print("执行下一个任务")
        p1.join()
        print("执行完毕")
    except KeyboardInterrupt as e:
        print(e)
    finally:
        print("Cleaning up...")
        # p1.kill()
        p1.terminate()
        p1.join()
        print(f"Process status: {p1.is_alive()}")

二、补充知识点

1. signal.SIGTERM 和 signal.SIGINT的区别

signal.SIGTERMsignal.SIGINT 都与终止程序相关,但它们并不完全相同。

  • signal.SIGINT 是由键盘输入产生的信号,通常通过按下 Ctrl+C 来触发,用于请求程序中断。
  • signal.SIGTERM 是由操作系统发送的终止信号,通常用于请求程序终止。在 Unix/Linux 系统中,kill 命令默认发送 SIGTERM 信号。

虽然它们都可以用于请求程序终止,但它们的使用场景略有不同:

  • 通常情况下,SIGINT 更多地用于用户主动请求程序终止,比如在终端中按下 Ctrl+C
  • SIGTERM 则更多地由系统或其他程序发送,用于通知程序终止,但程序可以选择是否立即响应这个信号,或是在一段时间后进行优雅地关闭。

2. Process(target=task, daemon=True)里的daemon

Process的daemon参数的默认值是False,当它是false的时候,如果创建的子进程没有执行完,主进程会一直等待。当然要注意的是虽然会阻塞主进程结束,但并不会阻塞在如下代码 p1.start() 这里,在程序运行的时候,不管p1进程有没有执行完,都会立即往下走,也就是会输出“执行到这里”。它的

p1 = Process(target=task, daemon=False)
p1.start()
print('执行到这里')

而当daemon=True 时,不会阻塞主进程结束,主进程可以立即结束。如果不想主进程结束,需要增加p1.join()来等待子进程结束。要注意p1.join是会阻塞下面语句执行的。所以要想启动多个子进程,一定要把多个子进程.start(), 写在上面,而把 join 写在最下面。例如:

p1 = Process(target=task, daemon=False)
p1.start()
p1 = Process(target=task, daemon=False)
p2.start()
p1.join()
p2.join()

总结

通过今天的学习我们能了解到闭包的一种巧妙用法、信号接收与处理,以及各自的好处。这样我们写代码的时候可以考虑的更多,而不只是被动依赖于操作系统对进程的处理,减少类似内存泄露、资源浪费等的问题的发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值