1

我有一个多线程 python 应用程序,其中产生线程来执行各种任务。这个应用程序已经运行了好几个月,但最近我遇到了一个问题。

其中一个线程启动了一个 pythonsubprocess.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?如果是这样,我该如何预防/处理这种情况?我能做些什么来进一步调试呢?

4

1 回答 1

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

当进程挂起时,我会尝试附加gdbpython进程以查看线程在做什么。也许这会提供一些想法。

编辑这是多线程 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 ()
...

hex(t.ident)您可以通过在 Python 代码中打印来确定哪个线程是哪个线程tthreading.Thread对象在哪里。在我的系统上,这与gdb( 0x7f82c6462700et al) 中看到的线程 ID 相匹配。

于 2011-06-03T10:11:09.797 回答