【问题标题】:A thread is blocked by a blocking call - how do I make a timeout on the blocking call?线程被阻塞调用阻塞 - 如何使阻塞调用超时?
【发布时间】:2013-11-16 01:50:58
【问题描述】:

我有一个 python 程序,它运行一个外部程序并启动一个超时线程。超时线程应该倒计时10分钟,如果运行外部程序的脚本在这段时间内没有完成,它应该杀死外部程序。

乍一看,我的线程似乎工作正常,我的主脚本和线程同时运行,没有任何问题。但是如果外部程序中出现一个弹出窗口,它会停止我的脚本,因此即使倒计时线程也停止计数,因此完全失败了。

我认为问题在于脚本在 API 中为外部程序调用了一个阻塞函数,该外部程序被弹出窗口阻塞。我理解它为什么会阻塞我的主程序,但不明白它为什么会阻塞我的倒计时线程。因此,一种可能的解决方案可能是为倒计时运行单独的脚本,但我希望它尽可能保持干净,为此启动脚本似乎真的很混乱。

我到处寻找线索,但没有找到太多线索。此处引用了 gevent 库background function in Python ,但这似乎是一项基本任务,我不想为此包含外部库。

我还找到了一个使用 Windows 多媒体计时器 here 的解决方案,但我以前从未使用过此功能,恐怕代码对此不灵活。脚本仅适用于 Windows,但它应该适用于从 XP 开始的所有 Windows。

对于 Unix,我发现 signal.alarm 似乎完全符合我的要求,但它不适用于 Windows。有什么替代方案吗?

关于如何以最简化的方式使用它的任何想法?

这是我正在创建的简化线程(在 IDLE 中运行以重现问题):

import threading
import time

class timeToKill():
    def __init__(self, minutesBeforeTimeout):
        self.stop = threading.Event()
        self.countdownFrom = minutesBeforeTimeout * 60

    def startCountdown(self):
        self.countdownThread= threading.Thread(target=self.countdown, args=(self.countdownFrom,))
        self.countdownThread.start()

    def stopCountdown(self):
        self.stop.set()
        self.countdownThread.join()

    def countdown(self,seconds):
        for second in range(seconds):
            if(self.stop.is_set()):
                break
            else:
                print (second)
                time.sleep(1)

timeout = timeToKill(1)
timeout.startCountdown()
raw_input("Blocking call, waiting for input:\n")

【问题讨论】:

  • 请给我们一些(希望是)小而完整的代码示例来显示问题,帮助我们重现问题。
  • 你究竟是如何“操作”外部程序的(调用阻塞函数)?
  • 我在我的示例中添加了一个示例阻塞调用。这个问题现在可以重现了。我希望我的计时器线程在等待输入时继续计数。
  • 我注意到,raw_input 仅在 IDE 中阻塞,如果您正常运行它则不会阻塞。因此,为了示例,请在 IDLE 中运行它。
  • 这不是一个相当的错误,但在您的代码中,您使用self.countdown 在不同时间表示两种不同的事物。在startCountdown 运行之前,它是一个方法,之后它是一个线程实例。您可能应该为其中一种用途选择不同的名称。但它不会改变任何事情,因为您只使用该方法作为线程的目标,并且此时它仍然可用。

标签: python multithreading timeout blocking


【解决方案1】:

对于阻塞另一个 Python 线程的函数调用的一个可能解释是 CPython 使用全局解释器锁 (GIL) 并且阻塞 API 调用不会释放它(注意:CPython 在阻塞 I/O 调用时释放 GIL,因此您的 @ 987654322@ 示例应该按原样工作)。

如果您无法通过错误的 API 调用来释放 GIL,那么您可以使用进程而不是线程,例如,multiprocessing.Process 而不是 threading.Thread(API 相同)。不同的进程不受 GIL 的限制。

【讨论】:

  • 我想知道为什么我的尝试失败了,这解释了这个问题。到目前为止,我有子进程模块的解决方案,但是在处理多个进程时,多处理似乎更好。感谢您的想法!
  • @AnjaV.:如果确实是问题所在,那么正确的解决方案是修复上游 API 以供外部程序在调用阻塞系统调用时释放 GIL。 multiprocessing.Process 只是一种解决方法。
【解决方案2】:

为了快速而肮脏的线程,我通常使用子进程命令。它非常健壮且独立于操作系统。它不像线程和队列模块那样提供细粒度的控制,但对于程序的外部调用通常做得很好。注意 shell=True 必须谨慎使用。

#this can be any command
p1 = subprocess.Popen(["python", "SUBSCRIPTS/TEST.py", "0"], shell=True)

#the thread p1 will run in the background - asynchronously.  If you want to kill it after some time, then you need 

#here do some other tasks/computations
time.sleep(10)

currentStatus = p1.poll()
if currentStatus is None: #then it is still running
  try:
    p1.kill() #maybe try os.kill(p1.pid,2) if p1.kill does not work
  except:
    #do something else if process is done running - maybe do nothing?
    pass

【讨论】:

  • 目前我正在使用与您的示例类似的子流程,尽管我希望可以避免它。请问为什么 shell=True 在您的示例中是必需的?
  • @Anja,shell=True 不是必需的。我经常死记硬背。除了子进程,你总是可以使用线程或队列模块
  • shell=True 与列表参数一起使用是不正确的,除非您想将其他参数传递给shell(它在等效于Popen(['/bin/sh', '-c'] + list_arg) 的POSIX 系统上会失败)。
  • 是的,我试图使用线程,但我的超时线程被阻塞,停止计数。你可能明白为什么我的例子中的超时线程在主程序到达阻塞调用时停止了吗?
猜你喜欢
  • 2014-03-23
  • 2014-11-18
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
  • 2021-01-20
  • 2012-01-25
  • 1970-01-01
  • 2021-10-19
相关资源
最近更新 更多