«Subprocess.Popen (). Readline ()» в многопоточном Python не может вернуться

1

Для ниже кода, запущенного в Win7, есть потоки T1 и T2, T1 печатает содержимое dir в исходном окне и T2 ping в течение 4 секунд в новом окне.

import os
import sys
import logging
import subprocess
import threading

class T1 (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        proc = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE)
        for line in iter(proc.stdout.readline, ''):
            logging.debug(line)
        logging.info("HEREEEEEEEE")

class T2 (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        subprocess.Popen(["ping.exe", "-n", "4", "127.0.0.1"], creationflags=subprocess.CREATE_NEW_CONSOLE)
        logging.info("")

if __name__=='__main__':
    logger = logging.getLogger('root')
    FORMAT = "[TID:%(thread)d %(funcName)s L#%(lineno)s] %(message)s"
    logging.basicConfig(format=FORMAT, level=logging.DEBUG)

    t1 = T1()
    t2 = T2()
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    sys.exit(0)

Для строки logging.info("HEREEEEEEEE") в потоке T1, я думаю, она должна быть напечатана сразу после logging.info("HEREEEEEEEE").

Что для меня не имеет смысла: почему строка не печатается сразу, а печатается через 4 секунды после завершения потока T2?

Интересно, связано ли это с файловым дескриптором в многопоточности.

  • 0
    @eryksun Он печатает содержимое каталога правильно, в любом случае я обновляю свой код, чтобы избежать путаницы.
  • 0
    @eryksun Да, я думаю, что это не ключ.
Показать ещё 1 комментарий
Теги:
multithreading
subprocess

1 ответ

1
Лучший ответ

Для Python 2 в вашем коде есть условие гонки, которое может протечь наследуемый конец записи, созданный потоком T1 в процесс ping.exe, созданный потоком T2. readline на трубе не вернется, пока труба не закроется, что требует, чтобы все ручки для конца записи были закрыты.

В этом случае вы можете избежать состояния гонки, передав close_fds=True в Popen при создании процесса ping.exe. Это предотвращает его наследование наследуемых ручек, включая дескриптор канала из перекрывающегося вызова в потоке T1.

В общем, до Python 3.7, если вам нужно поддерживать одновременные вызовы Popen с переопределенными стандартными дескрипторами, вам нужно будет обернуть Popen функцией, которая синхронизирует вызов, предварительно закрепив блокировку. К сожалению, это приводит к тому, что Popen называют узким местом в многопоточном процессе, но нет простой альтернативы.


Фон

В Unix параметр close_fds из Popen применяется в дочернем процессе после fork. Если это правда, то все нестандартные дескрипторы файлов будут закрыты перед вызовом exec, даже без флага FD_CLOEXEC. Однако он не закрывает стандартные дескрипторы файлов - stdin (0), stdout (1) и stderr (2).

В Windows дескриптор может быть помечен как наследуемый (то есть HANDLE_FLAG_INHERIT). По умолчанию дескрипторы не наследуются. Если CreateProcess вызывается с bInheritHandles как true, то все наследуемые дескрипторы наследуются дочерним bInheritHandles. Popen передает это как not close_fds, т. not close_fds Не закрывая дескрипторы файла, означает наследовать дескрипторы [*].

В Windows параметры stdin, stdout и stderr Popen используются для явного задания дочерних стандартных дескрипторов в STARTUPINFO (т.е. hStdInput, hStdOutput, hStdError). Если стандартные дескрипторы явно не переопределены, родительские стандартные дескрипторы неявно унаследованы консольными приложениями (например, python.exe), но не приложениями графического интерфейса (например, pythonw.exe). Если задано явно, ручки должны быть наследуемыми, а bInheritHandles (т. not close_fds) должны быть истинными. Это источник состояния гонки, когда другой поток выполняет перекрывающий вызов CreateProcess который также наследует дескрипторы.

В Python 3 частота этого состояния гонки уменьшается, если значение close_fds true, если стандартные дескрипторы не переопределены. В 3.7, он смягчается далее путем пропускания стандартных ручек в lpAttributeList области STARTUPINFOEX. При этом изменении одновременные вызовы Popen могут переопределять стандартные дескрипторы без утечки дескрипторов. Тем не менее, дескрипторы все еще должны быть наследуемыми, поэтому есть еще условие гонки с одновременными вызовами других функций, которые наследуют дескрипторы, такие как os.system и os.spawnl.


[*] Обратите внимание, что, несмотря на имя параметра, дескрипторы файлов C 'на самом деле не унаследованы в Windows. В то время как среда выполнения C может наследовать файловые дескрипторы для system и функции spawn/exec, ее использование STARTUPINFO для реализации этого недокументировано. Таким образом, Popen наследует только дескрипторы. При наследовании нестандартного дескриптора вам необходимо передать значение дескриптора msvcrt.get_osfhandle (например, через stdin, командную строку или переменную окружения), которую вы можете получить через msvcrt.get_osfhandle. Ребенок может открыть новый файловый дескриптор для унаследованного дескриптора через msvcrt.open_osfhandle.

Ещё вопросы

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