У меня есть multi- threaded python application, где потоки создаются для выполнения различных задач. Это приложение работает отлично в течение нескольких месяцев, но в последнее время я столкнулся с проблемой.
Один из потоков запускает объект 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? Если да, то как мне это предотвратить? Что я могу сделать, чтобы отладить это дальше?
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
, когда он зависает, чтобы посмотреть, что делают потоки. Возможно, это даст некоторые идеи.
edit Это то, что выглядит процесс с потоком multi- в 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, где t
является объектом threading.Thread
. В моей системе это совпадает с идентификаторами потоков, которые видны в gdb
(0x7f82c6462700
и др.).
top
иvmstat
для мониторинга таких вещей, как использование памяти / ЦП, подкачка страниц, ввод-вывод непосредственно перед и в то время, когда ваша программа не может прогрессировать.