【问题标题】:python 2.7: how to catch keyboard interrupt in program with more than 25 threadspython 2.7:如何在超过 25 个线程的程序中捕获键盘中断
【发布时间】:2023-03-29 13:36:01
【问题描述】:

我想在用户按下 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 个后无法捕获信号。

【问题讨论】:

  • 这对我来说很好。您使用的是什么操作系统?
  • 不需要线程中的KeyboardInterrupt
  • @JohanL Ubuntu14。出于好奇,如果将范围(30)增加到范围(1000),它仍然有效吗?
  • 在 1000 个线程时,我遇到了和你一样的问题。请参阅我对为什么以及您可以做些什么的回答。

标签: python multithreading python-2.7 keyboardinterrupt


【解决方案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)

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

【讨论】:

  • "当您创建多个线程时,线程创建将在很长一段时间内无法完成(因为它会不断被创建的线程中断,并且 GIL 会阻止它们并行运行)。 "答对了!我永远不会想到这一点。哇,在我使用其他语言的所有经验中,我从来没有遇到过这么糟糕的调度程序(即使在单处理器系统上)。很好的答案。是的,现在它可以工作了。谢谢。
  • 或者,如果我有足够的耐心让所有线程都被创建,我原来的例子会起作用....但是,正如我所说,我永远不会想到这一点。
  • daemon=True 更好。不知何故,我认为 3.0 之前的 Python 不支持这一点。我错了!感谢您指出这一点。实际上,我并不是第一个对 3.0 之前的守护线程感到困惑的人:github.com/dask/distributed/issues/18。因此,显然 Python 3.0 将 daemon 添加为 Thread 的参数,然后人们开始编写不是向后兼容方式的代码。
  • daemon=True 从 Python 2.6 开始可用,在此之前您可以使用 setDaemon 代替,所以我认为您链接到的页面有另一种类型的问题,但我没有知道。
  • 就 GIL 而言,这是一个令人讨厌的实现细节,但不仅仅是 Python 拥有它。例如。 Ruby 有类似的架构。线程适用于释放 GIL 的代码(通常是 numpy)并简化大部分等待的代码(通常是套接字)。对于需要更快运行的处理器绑定代码,请考虑改用multiprocessing。只要进程不进行通信或共享状态,那是相当直接的。如果您需要共享数据,它仍然是可能的,但需要更多的工作,通常使用队列。
【解决方案2】:

您可能想阅读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 操作调用。另外,提高 SystemExit 产生稍小的字节码(少 4 字节),(如果你 使用 from sys import exit 因为 sys.exit 预计返回 None, 所以包括一个额外的 POP_TOP)。

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

至于SystemExit如何处理,最终导致VM调用 os._exit,但在此之前,它会进行一些清理。它也运行 atexit._run_exitfuncs() 运行通过 退出模块。调用 os._exit 直接绕过 atexit 步骤。

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

【讨论】:

  • 我用raise SystemExit 代替os._exit(0) 进行了尝试。这让情况变得更糟了。使用raise SystemExit,则程序仅适用于 1 个线程(仅限主线程)。至少使用os._exit(0) 我可以获得 25 个线程。
  • 您能否在您的问题中添加一个编辑,显示您在将os._exit(0) 替换为raise SystemExit 时所做的事情?一对一的交换会改变允许的线程数似乎是不可思议的,但如果你说它会改变 - 那么在我的环境中测试你的代码来验证这个奇怪的现象会很有趣。
  • 为什么raise SystemExit 的行为与raise KeyboardInterrupt 有任何不同?鉴于后者已损坏(否则整个问题将不存在),我对前者也被损坏并不感到惊讶。
猜你喜欢
  • 1970-01-01
  • 2013-01-03
  • 2014-08-11
  • 1970-01-01
  • 1970-01-01
  • 2013-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多