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

evo*_*ion 3 python multithreading block freeze

在我看来,一个线程无缘无故地突然停止执行,并且再也没有恢复或重新启动。这种行为的可能原因是什么?不会抛出任何异常。至少没有检测到或打印出异常。它只是在没有任何信息的情况下停止。我的 pygtk GUI 和其他一切继续运行,没有问题。

它仅限于一段代码,但它发生在该段内的任何地方。以下代码在该线程内运行。我在我的代码中插入了很多打印,因为我无法调试它。调试器也会冻结。并且在没有调试器的情况下运行不会改变它(所以这不是调试的一些副作用)

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"
Run Code Online (Sandbox Code Playgroud)

该代码平均运行大约 3000-5000 次计数,并在不同的队列条目处停止(大部分 html 已缓存;))。挂起前的最后一个输出是随机的(每次我重新启动我的应用程序都是不同的)。大多数时候是 3,有时是 16。17 号永远不会到达。我也有一个 7。另一次它打印信息,但不再打印 5。还有一次它打印了一半的信息字符串。

由于大部分时间是 3,我怀疑那里有异常,并且可能有一些非常奇怪的延迟检测。但不是!打印“异常!” 在我的测试期间从未执行过。

我的线程在冻结后仍保留在内存中,并没有那么阻塞

self.htmlqueue.get(True)
Run Code Online (Sandbox Code Playgroud)

因为如果我这样做

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
Run Code Online (Sandbox Code Playgroud)

然后冻结继续,在大多数运行中根本没有“没有成功”的输出。更糟糕的是,我使用信号量一次一个地运行该代码部分,因此所有其他线程都被阻塞并等待这个线程完成。

主 GUI 线程继续运行而不受干扰。我自己的所有线程都应该在执行后死亡(我用 取消了它们mythread.setDaemon(True))。我的python版本是2.7.3

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

FileDownloader.downloadFileNewThread 从该线程内部启动另一个线程。这也会有问题吗?线程启动其他线程?如果我不妖魔化与妖魔化,这似乎也没有区别。

在我看来,代码确实会随机冻结。但为什么会这样?

evo*_*ion 7

好吧,伙计们,在我的头撞墙3天后,我想我解决了。至少没有什么悬而未决了。

pygtk 似乎是原因 http://faq.pygtk.org/index.py?file=faq20.006.htp&req=show

Pygtk 能够永远阻塞长时间运行的线程。它获取 GIL 锁并且不放手。该线程只是挂在内存中,但不再继续执行。

我所要做的就是打电话

gobject.threads_init()
Run Code Online (Sandbox Code Playgroud)

在与 gtk 相关的任何其他事情之前。换句话说,我的代码是这样开始的

import gtk
import gobject
import MainGUI

if __name__ == "__main__":
    gobject.threads_init()
    from MainGUI import MainGUI
    MainGUI.instance = MainGUI()
    gtk.main()
Run Code Online (Sandbox Code Playgroud)

幸运的是,我严格地将我的逻辑与 GUI 分开,并且从未在我自己的线程中实例化任何 gtk 元素。所以这是我真正需要的唯一一行。如果有人仍然需要能够在自己的线程中处理 GUI 对象,他必须使用

gobject.idle_add(callback_function, args)
Run Code Online (Sandbox Code Playgroud)

我学到的另一个教训是,队列的内存容量可能非常有限,如果塞满太多,它们也会导致阻塞行为。

Theads 启动另一个线程应该没有任何问题,因为据我现在所知,这些线程都是“在同一级别”启动的,python 不知道或维护它们之间的任何层次结构。这只是 pygtk 搞砸的关于 GIL(全局解释器锁)的伪随机斗争。