все потоки зависают при выполнении занятой задачи в одном потоке

1

У меня есть 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? Если да, то как мне это предотвратить? Что я могу сделать, чтобы отладить это дальше?

Теги:
multithreading
linux-kernel
gil
scheduler

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.

Я попробовал бы прикрепить 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 и др.).

  • 0
    Итак, я подождал, пока моя программа не застряла - я могу сказать из журналов, потому что я перестаю получать отладочные сообщения. Затем я попытался подключиться к нему с помощью GDB. это заняло некоторое время, я предполагаю, сколько времени моя программа "нормально" застрянет. Когда GDB наконец-то вернулся ко мне, я увидел все мои потоки в sem_wait, за исключением одного, который был в waitpid () и один, который был в clone () (это, вероятно, мой основной поток, который создает новые потоки). Означает ли преобладание sem_wait, что все просто ждет GIL?
  • 3
    @Igor: Первая часть вашего комментария намекает на узкое место более низкого уровня, чем GIL Python. Я бы начал с изучения процесса и вашей системы в top и vmstat для мониторинга таких вещей, как использование памяти / ЦП, подкачка страниц, ввод-вывод непосредственно перед и в то время, когда ваша программа не может прогрессировать.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню