Python线程似乎无故停止/冻结/挂起?可能原因?

5 投票
1 回答
8029 浏览
提问于 2025-04-20 12:40

我发现一个线程突然停止运行,没有任何原因,而且再也没有恢复或重新启动。可能是什么原因导致这种情况呢?没有抛出任何异常,至少没有检测到或打印出异常。它就这样停止了,完全没有任何信息。我使用的pygtk图形界面和其他一切都在正常运行。

这个问题只出现在一段特定的代码里,但在那段代码的任何地方都有可能发生。以下的代码是在那个线程里运行的。因为我无法调试,所以我在代码里插入了很多打印语句。调试器也会卡住。而且不使用调试器运行也没有改变这个情况(所以这不是调试带来的副作用)。

count = 0
while True: 
            count = count + 1
            #read results calculated by another thread. Done via Queue.Queue
            pos, phrase, site, extractor, infoextractor, image = self.htmlqueue.get(True)
            print "\"", threading.currentThread(), "\"", site, image["link"], infoextractor.__class__.__name__ , FileDownloader.nbdownloads, count
            print "1"
            #if Nones are found in the queue it means that there will be no more data
            if pos is None and phrase is None and site is None and extractor is None and infoextractor is None and image is None: break
            print "2"
            if printstuff: print "preDownloadAll1", image["link"],
            print "3"
            try:
                info = infoextractor.extractValues()
                print info
            except (object) as e:
                print "exception!"

            print "5"    
            if info is None: 
                print "_5.1_"
                continue
            print "6"
            if len(info) == 0: 
                print "_6.1_"
                continue
            print "7"

            if "google" in site:
                print "8"
                adr = image["smallthumb"]
                filename = ImageManager.extractFileFromURL(image["smallthumb"])
            elif info.has_key("thumb"):
                print "9"
                adr = info["thumb"]
                filename = ImageManager.extractFileFromURL(info["thumb"])
            else:
                print "10"
                adr = image["thumb"]
                filename = ImageManager.extractFileFromURL(image["thumb"])
            print "11" 
            localfile = self.imagelocations[site] + "/" + filename
            print "12"
            t = None
            if (not os.path.isfile(localfile)) and predownloadjpegs:
                print "13"
                t = FileDownloader.downloadFileNewThread(url = adr, localtargetdir = self.imagelocations[site], timetofinish = 100)
            print "14"
            tds.append((t, pos, phrase, site, extractor, infoextractor, image, info, adr, localfile))
            print "15"
            if count%100 == 0: print count, "\n"
            print "16"
#            seen[image["link"]] = True
        print "17"

这段代码平均运行大约3000到5000次,然后在不同的队列入口处停止(大部分html都是缓存的;))。在停止之前的最后输出是随机的(每次我重启应用程序时都不同)。大多数时候是3,有时是16。17这个数字从来没有达到过。我还见过7。有一次它打印了信息,但不再打印5。还有一次它只打印了一半的信息字符串。

因为大多数时候是3,所以我怀疑那里可能有异常,也许是某种非常奇怪的延迟检测。但并没有!在我的测试中,print "exception!"从来没有被执行过。

我的线程在冻结后仍然留在内存中,并不是完全阻塞。

self.htmlqueue.get(True)

因为如果我执行

pos, phrase, site, extractor, infoextractor, image = None, None, None, None, None, None
                success = False
                while not success:
                    try:
                        pos, phrase, site, extractor, infoextractor, image = self.htmlqueue.get(True,10)
                        success = True
                    except:
                        print "no success", count
                        success = False

那么冻结会继续,而且在大多数情况下根本没有“没有成功”的输出。更糟糕的是,我使用一个信号量来确保这段代码一次只运行一个线程,所以所有其他线程都被阻塞,等待这个线程完成。

主图形界面线程继续正常运行。所有我自己创建的线程在执行完后应该会结束(我用mythread.setDaemon(True)将它们设置为守护线程)。我的Python版本是2.7.3。

我还在考虑输出缓冲区的可能性,这可能让它看起来是随机的(实际上总是停在同一个地方,但某些输出可能仍然在输出缓冲区中)。但由于每个打印都会引入一个新行,我猜测每次输出都会立即刷新,所以在线程冻结的地方我不会错过任何输出。我的开发环境是eclipse和pydev。

FileDownloader.downloadFileNewThread在那个线程内部又启动了一个线程。这也会是个问题吗?线程启动其他线程?如果我不设置为守护线程和设置为守护线程似乎也没有什么区别。

我真的觉得这段代码是随机冻结的。但为什么会这样?这到底是怎么回事呢??

1 个回答

8

大家好,经过三天的苦苦挣扎,我觉得我找到了问题的解决办法。至少现在没有卡住的情况了。

问题似乎出在Pygtk上 http://faq.pygtk.org/index.py?file=faq20.006.htp&req=show

Pygtk可能会让一些长时间运行的线程一直处于阻塞状态。它会获取一个叫做GIL的锁,然后不放手。这样的话,线程就会在内存中挂着,但实际上并没有继续执行。

我只需要在任何与gtk相关的代码之前调用

gobject.threads_init()

换句话说,我的代码是这样开始的

import gtk
import gobject
import MainGUI

if __name__ == "__main__":
    gobject.threads_init()
    from MainGUI import MainGUI
    MainGUI.instance = MainGUI()
    gtk.main()

幸运的是,我把逻辑和界面分开得很清楚,从来没有在自己的线程里创建任何gtk元素。所以我只需要这一行代码。如果有人需要在自己的线程中处理GUI对象,就必须使用

gobject.idle_add(callback_function, args)

我还学到的另一个教训是,队列的内存容量可能会很有限,如果装得太满,也会导致阻塞的情况。

从一个线程再启动另一个线程应该没问题,因为据我现在的理解,这些线程都是“在同一层级”启动的,Python并不知道或维护它们之间的层级关系。只是因为Pygtk在争夺GIL(全局解释器锁)时搞得一团糟。

撰写回答