从multiprocessing源码理解其对SIGINT信号的屏蔽

28 篇文章 0 订阅
25 篇文章 0 订阅

问题引入

由于pythonGIL(全局解释器锁,Global Interpreter Lock),GIL在任意时刻只运行单个python线程执行,python的多线程并不能真正的利用多核CPU,无论有多少个核,程序都职能在一个处理器上运行。
要想利用多核并行计算,就必须使用多进程(直白点讲就是同时运行多个python程序)。关于多进程,python自带了multiprocessing。但在使用中发现,常用的Ctrl+C结束python程序的方法对multiprocessing是不适用的。下面就分析一下这个问题背后的原因。

想用Ctrl+C结束一个python程序,有两种方法。

处理KeyboardInterrupt异常

当用户键盘输入Ctrl+C时,python程序会抛出KeyboardInterrupt异常。只需要捕获这个异常时结束程序即可。

import sys

count=0
try:
    while True:
        count+=1
        print(count)
except KeyboardInterrupt as e:
    print('end')
    sys.exit(0)# 结束进程

处理SIGINT信号

用户按下Ctrl+Clinux内核会向进程发送SIGINT信号,只需在信号处理函数内退出即可。

import signal
import sys

def signal_handler(signal, frame):
    #print('end')# 注意,不能在信号处理函数内使用不可重入函数
    x = 3
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

count=0
while True:
    count+=1
    print(count)

multiprocessing多进程编程

下面是一个用multiprocessing写的多进程处理程序。

import multiprocessing
import time

def func(msg):
    while True:
        print('process-{}'.format(msg))
        time.sleep(1)

if __name__ == "__main__":
    P = multiprocessing.Pool(processes=4)# 4 threads
    for i in range(10):
        P.apply_async(func, (i,))
    P.close()
    P.join()

为了让该程序能处理Ctrl+C,尝试了上面两种方法。加KeyboardInterrupt异常处理,SIGINT信号处理,发现都不行。这是为什么呢?
Ctrl+C本质上就是让内核发送SIGINT信号,那我们先了解一下linux信号的机制。

linux信号机制

信号是进程间通信的一种机制。OS内核也可以因为内部事件,给进程发送信号(检查进程表中进程的状态,有选择的发送),通知进程发生了某个事件。

multiprocessing源码解释

在上例使用multiprocessing的代码中,我们首先创建进程池P,然后异步执行进程
这两步都做了些什么呢,我们可以到multiprocessing源码中看看。multiprocessingpython自带的库,所以其源码一般位于python安装路径的lib下,如C:\Python27\Lib\multiprocessing。通过源码可以发现:

  • 创建进程池P: 创建了Pool对象。
  • 异步执行进程: 执行Pool对象的成员函数apply_async,返回结果为ApplyResult类型的对象。

ApplyResult类的构造函数__init__中,第一行代码是threading.Condition(threading.Lock())Condition对象初始化时,就是被lock住的,SIGINT信号就没法被lock住后面的代码捕获,所以Ctrl+Cmultiprocessing无效。

结论

multiprocessing创建的每个进程,在进程执行函数结束(返回)之前,都是被lock住的。被lock住的代码没法接收到SIGINT信号。
关于怎么解决这个问题,[3]中有一些workaround。最好的解决办法,应该就是其中提到的用python3concurrent.futures包来做多进程,而不是用multiprocessing

参考

### 回答1: Python中的KeyboardInterrupt是一个异常,当用户在程序运行时按下Ctrl+C键时,就会触发这个异常。这个异常通常用于中断程序的执行,以便用户可以在需要时停止程序的运行。KeyboardInterrupt异常的出现通常是由于用户想要停止程序的执行,或者是由于程序出现了无限循环等问题导致的。 ### 回答2: PythonKeyboardInterrupt异常通常是由用户在程序运行期间按下“Ctrl + C”键触发的。当用户希望停止程序的执行时,通常会使用这个组合键。因此,这个异常通常是作为一种手动终止程序的方式。 当程序收到KeyboardInterrupt异常时,它会停止当前的操作并退出程序。所以,你可以在控制台或命令行中,通过 Keyboard Interrupt 来终止正在运行的 Python 程序。 KeyboardInterrupt除了用户手动中断程序外,它还可以在其他情况下被自动抛出。比如,当程序运行时间超过一定的时限,就可能会发生KeyboardInterrupt异常。 还有一种情况,就是当程序在执行某些输入/输出操作时被中断。比如,当你尝试读取一个长时间不可用的网络连接或者尝试从一个没有数据可供读取的管道中读取数据时,就可能会发生这种情况。 总之,KeyboardInterrupt异常是由用户手动终止程序或者程序在执行输入/输出操作时被中断所引起的异常,常用于强制停止正在运行的 Python 程序。 ### 回答3: PythonKeyboardInterrupt异常是由于用户在程序运行时使用了Ctrl + C或Ctrl + Break中断程序执行而引起的。通常情况下,程序运行时会等待用户的键盘输入,直到程序接收到用户的输入或等待超时。然而,当用户想要强制终止程序,以中止程序的执行时,他们可以通过按下Ctrl + C键或Ctrl + Break键来中断程序。这将触发一个KeyboardInterrupt异常,使程序跳出正在执行的代码块并退出程序。 KeyboardInterrupt异常支持的操作是由Python解释器处理的,当它发生时,Python解释器会立即停止程序的执行,并将控制权交给Python解释器,这意味着程序将结束其执行,并跳出正在执行的代码块。此时,解释器会发送一个中断信号给程序,使其停止执行。如果程序没有正确地处理KeyboardInterrupt异常,它可能会导致程序运行时发生其他错误。这些错误可能会导致程序异常退出或崩溃。 为了避免KeyboardInterrupt异常,需要在程序编写中注意以下几点: 1.使用try-except块捕获KeyboardInterrupt异常,以确保正确处理异常并停止程序的执行。 2.尝试使用信号处理模块,在程序遇到KeyboardInterrupt异常时执行清理操作,并安全地退出程序。 使用这些技巧可以有效地预防Python中的KeyboardInterrupt异常,并确保程序的正常运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值