在一个线程中运行繁忙任务时,所有线程都会挂起

时间:2011-06-03 09:45:04

标签: python multithreading linux-kernel scheduler gil

我有一个多线程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命令运行时,整个应用程序最终都会陷入困境,我的其他线程一次都没有运行几分钟。一旦copy完成,一切都会从中断的地方恢复。

我正在试图弄清楚如何防止这种情况发生。我最好的理论ATM是它与我的内核调度过程的方式有关。我将调用添加到setsid()以获得与主python应用程序分开安排的复制过程,但这没有效果。

我假设所有copy.wait()函数都是waitpid()。调用是否可能需要很长时间,在此期间一个线程持有GIL?如果是这样,我该如何预防/处理这个?我该怎么做才能进一步调试呢?

1 个答案:

答案 0 :(得分:2)

持有GIL的{p> copy.wait()也是我的第一个怀疑。但是,我的系统似乎不是这种情况(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));
}

当它在POSIX waitpid中被阻止时,这显然会释放GIL。

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

编辑这就是gdb中多线程Python流程的样子:

(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代码中打印hex(t.ident)来确定哪个线程是哪个,其中tthreading.Thread个对象。在我的系统上,这与gdb0x7f82c6462700等)中的线程ID匹配。