13

我有一个遵循标准范例的简单线程 Python 程序:

class SearchThread(threading.Thread):
    def __init__(self, search_queue):
        threading.Thread.__init__(self)
        self.search_queue = search_queue

    def run(self):
        while True:
            try:
                search_url = self.search_queue.get(timeout=15)
                # <do Internet search and print output/>
            except Queue.Empty:
                self.search_queue.task_done()
                break
            except Exception, e:
                print e

if __name__ == '__main__':
    search_queue = Queue.Queue()    
    for i in range(200):
        t = SearchThread(search_queue)
        t.setDaemon(True)
        t.start()
    search_queue.join()

队列中填充了大约 1000 个 url,并且HTTP GET<do Internet search and print output/>. 问题是,在处理了大约 500-700 个条目(只需要几秒钟)之后,程序总是永远挂起,没有输出,没有异常,什么都没有。

我试过requests, urllib2, urllib3,httplib2HTTP GET没有任何改变。

如何调试挂起的线程 Python 程序?

顺便说一句,我在 Ubuntu 11.10(64 位)下使用 Python 2.7。

编辑

当盯着挂起进程的gdb跟踪时,我和以前一样一无所知——

sudo gdb python 9602
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
...
(gdb) where
#0  0x00007fc09ea91300 in sem_wait () from /lib/x86_64-linux-gnu/libpthread.so.0
#1  0x00000000004ed001 in PyThread_acquire_lock ()
#2  0x00000000004f02de in ?? ()
#3  0x00000000004b6569 in PyEval_EvalFrameEx ()
#4  0x00000000004bcd2d in PyEval_EvalCodeEx ()
#5  0x00000000004b6a5b in PyEval_EvalFrameEx ()
#6  0x00000000004b6d77 in PyEval_EvalFrameEx ()
#7  0x00000000004bcd2d in PyEval_EvalCodeEx ()
#8  0x00000000004bd802 in PyEval_EvalCode ()
#9  0x00000000004dcc22 in ?? ()
#10 0x00000000004dd7e4 in PyRun_FileExFlags ()
#11 0x00000000004de2ee in PyRun_SimpleFileExFlags ()
#12 0x00000000004ee6dd in Py_Main ()
#13 0x00007fc09d86030d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#14 0x000000000041cb69 in _start ()
4

6 回答 6

16

我编写了一个模块,它打印出在一个地方挂起超过 10 秒的线程。 hang_threads.py

这是一个示例输出:

--------------------    Thread 5588     --------------------
  File "C:\python33\lib\threading.py", line 844, in _exitfunc
        t.join()
  File "C:\python33\lib\threading.py", line 743, in join
        self._block.wait()
  File "C:\python33\lib\threading.py", line 184, in wait
        waiter.acquire()

当您忘记将另一个线程设置为守护进程时,这会在主线程退出时发生。

于 2013-07-19T11:13:52.347 回答
2

似乎您面临与此线程中提到的相同的问题。

python多处理:某些函数完成后不返回(队列材料太大)

关键是这是一个未解决/关闭的错误吗?http://bugs.python.org/issue8237

于 2013-08-13T23:13:24.490 回答
2

我不确定你是否还有问题(问题有点老了......)。

它看起来像一个经典的死锁(因为它似乎挂在某个互斥锁上)。

对于 GDB,有一些不错的 Python 脚本可以使 C 回溯与 Python 调用的信息更加丰富。即它显示了实际的 Python 调用:

#3  0x00000000004b6569 in PyEval_EvalFrameEx ()
#4  0x00000000004bcd2d in PyEval_EvalCodeEx ()
#5  0x00000000004b6a5b in PyEval_EvalFrameEx ()
#6  0x00000000004b6d77 in PyEval_EvalFrameEx ()
#7  0x00000000004bcd2d in PyEval_EvalCodeEx ()
#8  0x00000000004bd802 in PyEval_EvalCode ()

我认为这些 GDB Python 脚本甚至包含在原始 Python 发行版中。去看一下。

然后,有一个很棒的faulthandler 模块,它为您提供了一些打印 Python 回溯的功能(例如在信号处理程序中)。在我的 MusicPlayer 项目中,我对它们进行了一些扩展,并大量使用它们进行调试。

例如,我添加了这个功能:

// This is expected to be called only from signal handlers (or in an evironment where all threads are stopped).
__attribute__((visibility("default")))
void _Py_DumpTracebackAllThreads(void) {
    PyInterpreterState* interp = NULL;
    PyThreadState* tstate = NULL;

    // The current active Python thread (that might not be us).
    tstate = _PyThreadState_Current;

    // No Python state is currently active. Try to get our own, if we have one assigned.
    if(!tstate)
        tstate = PyGILState_GetThisThreadState();

    // No thread found so far. Try the interpreter head.
    if(!tstate)
        interp = PyInterpreterState_Head();

    if(!interp && tstate)
        interp = tstate->interp;

    if(!interp) {
        printf("_Py_DumpTracebackAllThreads: no Python interpreter found\n");
        return;
    }

    _Py_DumpTracebackThreads(STDOUT_FILENO, interp, tstate);
}

现在,当我在 GDB 或 LLDB 中并想知道当前的 Python 线程时,我只需键入p _Py_DumpTracebackAllThreads()它就会打印在标准输出上。

除此之外,您对所有当前线程的 C 回溯感兴趣,即t apply all bt full应该打印 GDB 中的所有回溯。

如果那是它挂起的 Python GIL,则可能有一些其他活动的 Python 线程因为其他事情而挂起。那是实际的错误。它应该在此之前发布 Python GIL。

于 2014-02-22T13:50:55.060 回答
1

这个调试器可以调试多线程python程序:http ://winpdb.org/

于 2012-04-04T18:25:48.457 回答
0

您的 while 循环是无限的。即使队列为空,线程也永远不会完成执行。您应该检查队列是否有新任务或通知线程(例如使用事件)不需要任务。

于 2012-04-04T16:19:31.703 回答
0

还有一件事是滥用 Queue.get。第一个参数是一个布尔值“块”。您应该输入如下内容:

self.search_queue.get(timeout=15)

而且,正如我上面写的,避免使用无限循环。当您的超时到期时,Queue.get 会引发“Empty”异常,该异常会被“except Exception”(另一种应避免使用的构造)捕获。所以你的循环真的是无限的。您将“例外”更改为

except Queue.Empty:
    self.search_queue.task_done()
    break

编辑

最初的问题代码如下

self.search_queue.get(15)
于 2012-04-04T20:28:43.020 回答