Catch KeyboardInterrupt或处理线程中的信号

时间:2015-05-15 19:48:43

标签: python multithreading python-3.x signals

我有一些线程正在运行,其中一个线程包含一个将产生子进程的对象。我希望一个这样的子进程能够杀死整个应用程序。上述对象在接收到该信号时需要保存一些状态。不幸的是,我无法在导致杀死的线程中处理信号。

以下是一些尝试复制情况的示例代码。

parent.py :启动一个帖子。该线程运行一些子进程,其中一个子进程将尝试终止父进程。

#!/usr/local/bin/python3
import subprocess, time, threading, random

def killer_func():
    possible_cmds = [['echo', 'hello'],
                     ['echo', 'world'],
                     ['/work/turbulencetoo/tmp/killer.py']
                     ]
    random.shuffle(possible_cmds)
    for cmd in possible_cmds:
        try:
            time.sleep(2)
            subprocess.check_call(cmd)
            time.sleep(2)
        except KeyboardInterrupt:
            print("Kill -2 caught properly!!")
            print("Here I could properly save my state")
            break
        except Exception as e:
            print("Unhandled Exception: {}".format(e))
        else:
            print("No Exception")

killer_thread = threading.Thread(target=killer_func)
killer_thread.start()
try:
    while True:
        killer_thread.join(4)
        if not killer_thread.is_alive():
            print("The killer thread has died")
            break
        else:
            print("Killer thread still alive, try to join again.")
except KeyboardInterrupt:
    print("Caught the kill -2 in the main thread :(")

print("Main program shutting down")

killer.py ,一个试图用SIGINT杀死其父进程的简单程序:

#!/usr/local/bin/python3
import time, os, subprocess, sys

ppid = os.getppid()

# -2 specifies SIGINT, python handles this as a KeyboardInterrupt exception
cmd = ["kill", "-2", "{}".format(ppid)]

subprocess.check_call(cmd)
time.sleep(3)

sys.exit(0)

以下是运行父程序的一些示例输出:

$ ./parent.py
hello
Killer thread still alive, try to join again.
No Exception
Killer thread still alive, try to join again.
Caught the kill -2 in the main thread :(
Main program shutting down
No Exception
world
No Exception

我尝试在signal.signal()中使用killer_func,但它在子线程中不起作用。

有没有办法强制函数处理信号或异常,而主线程不知道?

2 个答案:

答案 0 :(得分:1)

程序的主线程始终是接收信号的线程。 signal module documentation说明了这一点:

  

如果使用信号和线程,必须要小心   同样的计划。使用信号和记忆时要记住的基本要点   线程同时是:始终执行signal()操作   主要执行线程。任何线程都可以执行alarm(),   getsignal()pause()setitimer()getitimer();只有主线程   可以设置一个新的信号处理程序,,主线程将是唯一的   接收信号(这是由Python signal模块强制执行的   如果底层线程实现支持发送信号   个别线程)。这意味着信号不能用作手段   线程间通信。改为使用锁。

您需要重构您的程序,以便接收信号的主线程不会阻止您保存状态。最简单的方法是使用threading.Event()之类的东西告诉后台线程程序已经中止,并在看到事件设置时让它清理:

import subprocess
import threading
import random

def killer_func(event):
    possible_cmds = [['echo', 'hello'],
                     ['echo', 'world'],
                     ['/home/cycdev/killer.py']
                     ]
    random.shuffle(possible_cmds)
    for cmd in possible_cmds:
        subprocess.check_call(cmd)
        event.wait(4)
        if event.is_set():
            print("Main thread got a signal. Time to clean up")
            # save state here.
            return

event = threading.Event()
killer_thread = threading.Thread(target=killer_func, args=(event,))
killer_thread.start()
try:
    killer_thread.join()
except KeyboardInterrupt:
    print("Caught the kill -2 in the main thread :)")
    event.set()
    killer_thread.join()

print("Main program shutting down")

答案 1 :(得分:1)

始终在主线程中处理信号。当您收到信号时,您不知道它来自哪里。你不能说“在产生信号发送过程的线程中处理它”,因为你不知道信号发送过程是什么。

解决此问题的方法是使用Condition Variables通知所有线程收到信号并且必须关闭它们。

import threading

got_interrupt = False   # global variable

def killer_func(cv):
    ...
    with cv:
        cv.wait(2)
        interupted = got_interrupt  # Read got_interrupt while holding the lock
    if interrupted:
        cleanup()
    ...


lock = threading.Lock()
notifier_cv = threading.Condition(lock)
killer_thread = threading.Thread(target=killer_func, args=(notifier_cv,))
killer_thread.start()
try:
    ...
except KeyboardInterrupt:
    with cv:
        got_interrupt = True
        cv.notify_all()