在Python中,是否可能从子线程内杀死父线程?

15

我正在使用Windows上的Python 3.5.2。

我想要运行一个Python脚本,但要确保它不会超过N秒。如果它超过了N秒,就应该引发异常并退出程序。最初,我认为只需在开始时启动一个线程,等待N秒钟后抛出异常,但这只能将异常抛给计时器线程,而不是父线程。例如:

import threading
import time

def may_take_a_long_time(name, wait_time):
    print("{} started...".format(name))
    time.sleep(wait_time)
    print("{} finished!.".format(name))

def kill():
    time.sleep(3)
    raise TimeoutError("No more time!")

kill_thread = threading.Thread(target=kill)
kill_thread.start()

may_take_a_long_time("A", 2)
may_take_a_long_time("B", 2)
may_take_a_long_time("C", 2)
may_take_a_long_time("D", 2)

这将输出:

A started...
A finished!.
B started...
Exception in thread Thread-1:
Traceback (most recent call last):
    File "C:\Program Files\Python35\lib\threading.py", line 914, in _bootstrap_inner
    self.run()
    File "C:\Program Files\Python35\lib\threading.py", line 862, in run
    self._target(*self._args, **self._kwargs)
    File "timeout.py", line 11, in kill
    raise TimeoutError("No more time!")
    TimeoutError: No more time!

B finished!.
C started...
C finished!.
D started...
D finished!.

这有丝毫可能吗?我意识到我可以做类似这样的事情:

import threading
import time

def may_take_a_long_time(name, wait_time, thread):
    if not thread.is_alive():
        return
    print("{} started...".format(name))
    time.sleep(wait_time)
    print("{} finished!.".format(name))

def kill():
    time.sleep(3)
    raise TimeoutError("No more time!")

kill_thread = threading.Thread(target=kill)
kill_thread.start()

may_take_a_long_time("A", 2, kill_thread)
may_take_a_long_time("B", 2, kill_thread)
may_take_a_long_time("C", 2, kill_thread)
may_take_a_long_time("D", 2, kill_thread)

但是如果例如调用了may_take_a_long_time("B", 60, kill_thread),那么这种方法就会失败。

所以我想问的问题是,如何在主线程本身上设置时间限制?


你无法完全“杀死”Python线程。没有相应的API。而你在代码中所做的只是在“新线程”中引发异常,这不会影响主线程。 - ForceBru
时间限制的目的是什么?如果你不想等待某件事情超过一定的时间,你可以选择停止等待。 - David Schwartz
@ForceBru 我知道它不会影响主要程序,这就是我的问题。这是我最初的天真实现。我正在尝试找出如何确保函数系列(may_take_a_long_time)不会超过N秒钟。 - DJMcMayhem
3个回答

19

您可以使用_thread.interrupt_main(在Python 2.7中,此模块称为thread):

import time, threading, _thread

def long_running():
    while True:
        print('Hello')

def stopper(sec):
    time.sleep(sec)
    print('Exiting...')
    _thread.interrupt_main()

threading.Thread(target = stopper, args = (2, )).start()

long_running()

嘿,谢谢!这正是我在寻找的(而且速度也很快!)。你知道是否可以更改抛出的异常吗?KeyboardInterrupt似乎与我正在做的事情不太相关。 - DJMcMayhem
@DJMcMayhem,文档中说了:此函数仅引发KeyboardInterrupt异常。实际上,您可以使用全局变量来表示该异常是由stopper引发的,并以不同方式处理此情况。但是,如果您在主线程中捕获异常,则长时间运行的函数将被终止,其结果将丢失,因此您可能希望在函数内部处理它。 - ForceBru
我在子线程中使用了 @ForceBru 的建议,并按照 @rbanffy 的建议调用 os._exit(0) 来处理 KeyboardInterrupt - Casey Jones

6
如果父线程是根线程,你可能想尝试使用 os._exit(0)
import os
import threading
import time

def may_take_a_long_time(name, wait_time):
    print("{} started...".format(name))
    time.sleep(wait_time)
    print("{} finished!.".format(name))

def kill():
    time.sleep(3)
    os._exit(0)

kill_thread = threading.Thread(target=kill)
kill_thread.start()

may_take_a_long_time("A", 2)
may_take_a_long_time("B", 2)
may_take_a_long_time("C", 2)
may_take_a_long_time("D", 2)

1
不幸的是,那样做行不通。我猜测 sys.exit() 只限于调用它的线程,没有任何改变父线程行为的方法。 - DJMcMayhem
那样做效果好多了,但我可能不太可能使用它,因为它实际上并不会引发异常或抛出错误。 - DJMcMayhem
你可以返回其他内容给调用进程以表示超时。通常,返回0表示一切正常。我不确定在Windows上可以返回什么(上次我检查时,在DOS中你能够返回一个int,但那是很久以前的事了)。 - rbanffy

-1

最干净的方法是拥有一个队列,父线程定期检查该队列。如果子线程想要杀死父线程,它会向队列发送一条消息(例如“DIE”)。父线程检查队列,如果看到这条消息,就会停止运行。

    q = queue.Queue()
    bc = queue.Queue()          # backchannel

    def worker():
    while True:
            key = q.get()
            try:
                process_key(key)
            except ValueError as e:
                bc.put('DIE')
            q.task_done()

    # Start the threads
    for i in range(args.threads):
        threading.Thread(target=worker, daemon=True).start()

    for obj in object_farm():
        q.put(obj)
        try:
            back = bc.get(block=False)
        except queue.Empty:
            pass
        else:
            print("Data received on backchannel:",back)
            if back=='DIE':
                raise RuntimeError("time to die")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接