2011-06-03 46 views
1

我有一个多线程的python应用程序,其中线程被派生出来执行各种任务。这个应用程序已经运行了好几个月,但最近我遇到了一个问题。在一个线程中运行繁忙任务时,所有线程挂起

其中一个线程启动运行密集数据复制命令的python subprocess.Popen对象。

copy = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, preexec_fn = os.setsid, shell = False, close_fds = True) 
if copy.wait(): 
    raise Exception("Unable to copy!") 

虽然复制命令运行时,整个应用程序最终停滞不前,与我没有任何其他线程在一个时间几分钟内运行。一旦copy完成,所有事情都会从停止的地方恢复。

我想弄清楚如何防止这种情况发生。我最好的理论ATM是它与我的内核调度过程的方式有关。我添加了对setsid()的调用以使复制进程与主python应用程序分开计划,但这不起作用。

我假设所有copy.wait()功能确实是waitpid()。有可能这个调用需要很长时间,在这个过程中,一个线程持有GIL?如果是这样,我该如何预防/处理这个问题?我能做些什么来进一步调试?

回答

2

copy.wait()抱着GIL是我第一次怀疑。但是,我的系统似乎并未出现这种情况(wait()调用并未阻止其他线程的进展)。

你是对的copy.wait()最终在os.waitpid()结束。后者看起来像这样我的Linux系统上:

PyDoc_STRVAR(posix_waitpid__doc__, 
"waitpid(pid, options) -> (pid, status)\n\n\ 
Wait for completion of a given child process."); 

static PyObject * 
posix_waitpid(PyObject *self, PyObject *args) 
{ 
    pid_t pid; 
    int options; 
    WAIT_TYPE status; 
    WAIT_STATUS_INT(status) = 0; 

    if (!PyArg_ParseTuple(args, PARSE_PID "i:waitpid", &pid, &options)) 
     return NULL; 
    Py_BEGIN_ALLOW_THREADS 
    pid = waitpid(pid, &status, options); 
    Py_END_ALLOW_THREADS 
    if (pid == -1) 
     return posix_error(); 

    return Py_BuildValue("Ni", PyLong_FromPid(pid), WAIT_STATUS_INT(status)); 
} 

这显然释放了GIL,而它的挡在POSIX waitpid

我想尝试将gdb附加到python进程挂起来看看线程正在做什么。也许这会提供一些想法。

编辑这是一个多线程的Python程序看起来像gdb

(gdb) info threads 
    11 Thread 0x7f82c6462700 (LWP 30865) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    10 Thread 0x7f82c5c61700 (LWP 30866) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    9 Thread 0x7f82c5460700 (LWP 30867) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    8 Thread 0x7f82c4c5f700 (LWP 30868) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    7 Thread 0x7f82c445e700 (LWP 30869) 0x00000000004a3c37 in PyEval_EvalFrameEx() 
    6 Thread 0x7f82c3c5d700 (LWP 30870) 0x00007f82c7676dcd in sem_post() from /lib/libpthread.so.0 
    5 Thread 0x7f82c345c700 (LWP 30871) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    4 Thread 0x7f82c2c5b700 (LWP 30872) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    3 Thread 0x7f82c245a700 (LWP 30873) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
    2 Thread 0x7f82c1c59700 (LWP 30874) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
* 1 Thread 0x7f82c7a7c700 (LWP 30864) 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 

在这里,所有的线程除了两个正在等待GIL。一个典型的堆栈跟踪是这样的:

(gdb) thread 11 
[Switching to thread 11 (Thread 0x7f82c6462700 (LWP 30865))] #0 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
(gdb) where 
#0 0x00007f82c7676b50 in sem_wait() from /lib/libpthread.so.0 
#1 0x00000000004d4498 in PyThread_acquire_lock() 
#2 0x00000000004a2f3f in PyEval_EvalFrameEx() 
#3 0x00000000004a9671 in PyEval_EvalCodeEx() 
... 

你可以找出哪些线程是由你的Python代码,其中tthreading.Thread对象打印hex(t.ident)。在我的系统中,这与在gdb0x7f82c6462700等)中看到的线程ID匹配。

+0

所以,我一直等到我的程序被卡住了 - 我可以从日志中知道,因为我停止收到调试消息。然后我尝试用gdb附加到它。这花了一些时间,我猜测我的程序将“正常”卡住的时间长度。当gdb最终回到我身边时,我看到了所有在sem_wait中的线程,除了在waitpid()中的线程和在clone()中的线程(这可能是我的主线程,它正在创建新线程)。 做sem_wait的优势意味着一切都只是在等待GIL? – 2011-06-03 10:47:02

+3

@Igor:你的评论的第一部分暗示了比Python的GIL更低的瓶颈。我会首先检查'top'和'vmstat'中的进程和系统来监视内存/ CPU使用率,分页,I/O等事情,然后在程序无法进展的时间和期间。 – NPE 2011-06-03 10:50:49