终止长时间运行的Python线程

5 投票
4 回答
7140 浏览
提问于 2025-04-15 13:22

在Python中,如何优雅地结束那些运行时间过长的线程呢?我不能使用SIGALRM,因为

如果在同一个程序中同时使用信号和线程,需要特别小心。使用信号和线程的关键点是:所有的信号操作都必须在主线程中进行。任何线程都可以执行alarm()、getsignal()、pause()、setitimer()或getitimer()这些操作;但是只有主线程可以设置新的信号处理器,并且只有主线程会接收到信号(这一点是Python信号模块强制执行的,即使底层线程实现支持向单独线程发送信号)。这意味着信号不能用作线程之间的通信方式,应该使用锁来代替。

更新:在我的情况下,每个线程都在阻塞——它正在使用urllib2模块下载网页,有时候在极慢的网站上操作会花费太多时间。这就是我想要结束这些慢线程的原因。

4 个回答

1

使用同步对象,并请求线程终止。简单来说,就是要写一些合作的处理方式。

如果你直接强行结束Python解释器下的线程,可能会发生各种奇怪的事情,这不仅仅是在Python中,大多数运行环境都有这个问题。

举个例子,如果你在一个线程打开了一个文件后就把它杀掉,那么这个文件就不会被关闭,直到应用程序完全结束。

5

正如Alex Martelli所建议的,你可以使用多进程模块。这和线程模块很相似,所以你可以很容易地开始。你的代码可以像这样:

import multiprocessing

def get_page(*args, **kwargs):
    # your web page downloading code goes here

def start_get_page(timeout, *args, **kwargs):
    p = multiprocessing.Process(target=get_page, args=args, kwargs=kwargs)
    p.start()
    p.join(timeout)
    if p.is_alive():
        # stop the downloading 'thread'
        p.terminate()
        # and then do any post-error processing here

if __name__ == "__main__":
    start_get_page(timeout, *args, **kwargs)

当然,你需要以某种方式获取你下载页面的返回值。为此,你可以使用multiprocessing.Pipe或者multiprocessing.Queue(或者多进程模块提供的其他方法)。你可以在这里找到更多信息和示例。

最后,多进程模块在Python 2.6中已经包含。如果你使用的是Python 2.5或2.4,也可以在pypi上找到(你可以使用easy_install multiprocessing)或者直接访问pypi手动下载和安装这些包。

注意:我知道这个问题已经发布了一段时间。我遇到了类似的问题,偶然间看到了这里,看到Alex Martelli的建议。于是我把它应用到我的问题上,并决定分享出来。(我想感谢Alex给我指明了方向。)

6

因为直接强制结束一个正在执行阻塞操作的线程是不可行的,所以更好的方法是,如果可以的话,尽量避免使用线程,而是选择其他不会出现这种问题的多任务处理方式。

对于提问者的具体情况(线程的工作是下载网页,有些线程因为网站的问题而一直卡住),理想的解决方案是twisted,因为它通常适合处理网络任务。在其他情况下,使用multiprocessing可能会更好。

更一般来说,当线程出现无法解决的问题时,我建议换用其他多任务处理方式,而不是尝试一些极端的方法来让线程完成那些在CPython中不适合的任务。

撰写回答