python 2.7:如何在超过25个线程的程序中捕获键盘中断

时间:2017-09-11 18:08:24

标签: python multithreading python-2.7 keyboardinterrupt

我想在用户按下ctrl-C时停止我的程序。 以下答案建议捕获KeyboardInterrupt例外。

python: how to terminate a thread when main program ends

有时它有效。但是在下面的示例中,我将线程数从25增加到30后停止工作。

import threading, sys, signal, os

stderr_lock = threading.Lock()

def Log(module, msg):
    with stderr_lock:
        sys.stderr.write("%s: %s\n" % (module, msg))

class My_Thread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        Log("Init", "Initing.")
        self.start()
    def run(self):
        try:
            while True:
                Log("Run", "Running.")
        except KeyboardInterrupt:
            os._exit(0)

for i in range(30):
    My_Thread()

# trap ctrl-C in main thread
try:
    while True:
        pass
except KeyboardInterrupt:
    os._exit(0)

这与以下问题有着非常相似的感觉:

Thread-Safe Signal API in Python 2.7

在这种情况下,我将线程数增加到87以后无法捕获信号。

2 个答案:

答案 0 :(得分:1)

实际上,您的代码存在两个不同的问题,可以解决此问题。第一个是你的线程应该成为daemon个线程,这样它们会在主线程退出时自动停止,第二个是你的try块没有封装线程的创建和启动。

当你创建多​​个线程时,线程创建将不会完成很长一段时间(因为它被创建的线程不断中断,而GIL阻止它们并行运行)。因此,您在设置处理之前发送KeyboardInterrupt。但是,KeyboardInterrupt仍将终止主线程(使用Traceback),但不会终止子线程。

因此,如果您将代码修改为:

,则代码可以正常工作
import threading, sys, signal, os

stderr_lock = threading.Lock()

def Log(module, msg):
    with stderr_lock:
        sys.stderr.write("%s: %s\n" % (module, msg))

class My_Thread(threading.Thread):
    def __init__(self, value):
        threading.Thread.__init__(self)
        self.value = value
        Log("Init", "Initing %d." % self.value)
        self.daemon = True
        self.start()
    def run(self):
        while True:
            Log("Run", "Running %d." % self.value)

# trap ctrl-C in main thread
try:
    for i in range(1000):
        My_Thread(i)

    while True:
        pass
except KeyboardInterrupt:
    os._exit(0)

请注意,在当前示例中,将线程设置为守护进程并不是绝对必要的,但我认为对于应该在主程序结束时结束的线程来说这是一个很好的做法。

答案 1 :(得分:0)

您可能需要阅读https://stackoverflow.com/a/35430500/1656850,即:

  

除了提升SystemExit外,还有3个退出功能。

     

底层的是os._exit,它需要1个int参数,和   立即退出,没有清理。你不太可能想要   触摸这个,但它就在那里。

     

sys.exit在sysmodule.c中定义,只是运行   PyErr_SetObject(PyExc_SystemExit,exit_code);,这是有效的   与直接提升SystemExit相同。细节,提高   SystemExit可能更快,因为sys.exit需要LOAD_ATTR   和CALL_FUNCTION与RAISE_VARARGS opcalls。另外,提高SystemExit   产生稍小的字节码(少4个字节),(如果你的话,额外增加1个字节)   从sys导入退出使用,因为sys.exit应该返回None,   所以包括一个额外的POP_TOP)。

     

最后一个退出函数在site.py中定义,别名为exit或   退出REPL。它实际上是Quitter类的一个实例(所以   它可以有一个自定义的 repr ,因此可能是最慢的运行。   此外,它在提升SystemExit之前关闭sys.stdin,所以它是   建议仅在REPL中使用。

     

至于如何处理SystemExit,最终会导致VM调用   os._exit,但在此之前,它做了一些清理工作。它也运行   atexit._run_exitfuncs()运行通过注册的任何回调   atexit模块。直接调用os._exit会绕过atexit步骤。

因此,raise SystemExit可能是捕获异常时退出的首选方式。