Мой скрипт принимает на вход большое количество файлов, вызывая внешние программы с помощью subprocess.call()
. Он отлично работает на обычных консолях ОС, но зависает после обработки примерно 10 файлов при работе во встроенной среде с использованием встроенного Python 2.7.x.
Я ссылался на различные подобные вопросы и не смог найти ни одного, работающего на меня:
Python: subprocess.Popen и subprocess.call зависают
Вызов подпроцесса Python зависает
и это подробное обсуждение: https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/
Все они подразумевают уязвимый буферизованный PIPE и предлагают использовать файловые объекты для stdout
и stderr
. Поэтому я также добавил временный текстовый файл, открыл его и скормил stdout
и stderr
из subprocess.call()
. Это тоже не сработало.
Мой старый код довольно прост:
# script1.py
folder = "/path/to/my/folder"
for root, dirs, files in os.walk(folder):
for file in files:
path = join(root, file)
try:
cmd = ['dir', path, '1>&2']
_logger.debug(' '.join(cmd))
completed = subprocess.call(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as err:
_logger.debug(err)
else:
_logger.debug('returncode: {}'.format(completed))
print('all done!!')
основной скрипт:
try:
cmd = ['python', 'script1.py', '1>&2']
ue.log(' '.join(cmd))
completed = subprocess.call(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as err:
_logger.error(err)
Я смешал и сопоставил пару решений stdout/stderr. в том числе добавление:
with open(join(_script_dir, 'tmp.txt'), 'w') as tmp:
#
# old code
#
completed = subprocess.call(cmd, shell=True, stdout=tmp, stderr=tmp)
Ни один не работал.
В итоге я выбросил все каналы и stdout/stderr и использовал аргументы subprocess.Popen()
Python 3.7.2 по умолчанию, то есть None
для stdout/stderr, и удалил 1>&2
. Так что в основном просто не утруждайте себя получением вывода другой программы в реальном времени, когда программа работает с большим объемом ввода и потенциально генерирует огромную волну вывода через узкий и скрытый канал. Я перешел к использованию только лог-файлов, написанных для каждого отдельного скрипта.
Я гораздо более доволен этим решением, чем слепо пробовать всевозможные трюки UNIX для решения проблемы канала в Windows.
P.S.,
Некоторые ответы из моих приведенных выше ссылок предложили shell=False
, я обнаружил, что это раздражает тем, что встроенная среда будет порождать множество плавающих окон консоли из каждого subprocess.call()
.
Есть решение проблемы с висящей трубой, которое заключается в использовании from multiprocessing import Manager
Вы настраиваете очередь задач:
mgr = Manager()
task_queue = mgr.Queue()
Передайте его процессу в качестве аргумента:
gmat_args.append([gmat_arg, task_queue])
...
pool = Pool(processes=nrunp, maxtasksperchild=20)
...
results = pool.map(run_gmat, gmat_args, chunksize=ninstances)
Процесс пишет в пул:
def run_gmat(args):
q = args[1]
scriptname = os.path.basename(args[0])
proc = sp.Popen(['gmat', '-m', '-ns', '-x', '-r', str(args[0])])
(outs, errors) = proc.communicate(timeout=cpto)
outs = outs.decode('UTF-8')
q.put(filter_outs(outs, scriptname))
Затем, вернувшись в основной процесс, вы читаете очередь и регистрируете ее:
while 1:
qout = task_queue.get(cpto)
logging.info(qout)
if task_queue.qsize() < 1:
break
Я не могу сказать, что это идеально, я выполняю более 4000 заданий и получаю 2-3 тайм-аута, которые, похоже, связаны с файловым вводом-выводом (задание записывает массивный файл отчета по завершении). Чтобы предотвратить зависание, я ловлю исключение тайм-аута, записываю последний stdout, stderr в очередь и убиваю задание. Я теряю файлы отчетов, но я вижу тайм-аут в журнале из очереди, так что это просто вопрос повторного запуска этих 2-3 заданий.