我有一个多线程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?如果是这样,我该如何预防/处理这个?我该怎么做才能进一步调试呢?
答案 0 :(得分:2)
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)
来确定哪个线程是哪个,其中t
是threading.Thread
个对象。在我的系统上,这与gdb
(0x7f82c6462700
等)中的线程ID匹配。