Неблокирующий subprocess.call
Я пытаюсь сделать неблокирующий вызов подпроцесса, чтобы запустить скрипт slave.py из моей программы main.py. Мне нужно передать аргументы из main.py в slave.py один раз, когда он (slave.py) впервые запускается через subprocess.call после того, как этот slave.py запускается в течение определенного периода времени, а затем завершается.
main.py
for insert, (list) in enumerate(list, start =1):
sys.args = [list]
subprocess.call(["python", "slave.py", sys.args], shell = True)
{loop through program and do more stuff..}
И мой рабский сценарий
slave.py
print sys.args
while True:
{do stuff with args in loop till finished}
time.sleep(30)
В настоящее время slave.py блокирует main.py от выполнения остальных задач, я просто хочу, чтобы slave.py был независимым от main.py, как только я передал ему аргументы. Эти два сценария больше не должны общаться.
Я нашел несколько постов в сети о неблокирующих subprocess.call, но большинство из них сосредоточены на том, что в какой-то момент мне требуется связь с slave.py, которая мне сейчас не нужна. Кто-нибудь знает, как реализовать это простым способом...?
5 ответов
Вы должны использовать subprocess.Popen
вместо subprocess.call
,
Что-то вроде:
subprocess.Popen(["python", "slave.py"] + sys.argv[1:])
Из документов наsubprocess.call
:
Запустите команду, описанную аргументами. Дождитесь завершения команды, затем верните атрибут кода возврата.
(Также не используйте список для передачи аргументов, если вы собираетесь использовать shell = True
).
Вот пример MCVE1, который демонстрирует неблокирующий вызов suprocess:
import subprocess
import time
p = subprocess.Popen(['sleep', '5'])
while p.poll() is None:
print('Still sleeping')
time.sleep(1)
print('Not sleeping any longer. Exited with returncode %d' % p.returncode)
Альтернативный подход, основанный на более недавних изменениях языка Python для обеспечения параллелизма на основе подпрограмм:
# python3.5 required but could be modified to work with python3.4.
import asyncio
async def do_subprocess():
print('Subprocess sleeping')
proc = await asyncio.create_subprocess_exec('sleep', '5')
returncode = await proc.wait()
print('Subprocess done sleeping. Return code = %d' % returncode)
async def sleep_report(number):
for i in range(number + 1):
print('Slept for %d seconds' % i)
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(do_subprocess()),
asyncio.ensure_future(sleep_report(5)),
]
loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
1 Протестировано на OS-X с использованием python2.7 и python3.6
Здесь есть три уровня тщательности.
Как говорит Мгилсон, если вы просто поменяетесь subprocess.call
за subprocess.Popen
Если все остальное оставить прежним, main.py не будет ждать окончания slave.py, прежде чем продолжить. Этого может быть достаточно само по себе. Если вам небезразличны процессы, связанные с зомби, вам следует сохранить объект, возвращенный из subprocess.Popen
и в какой-то более поздний момент назвать его wait
метод. (Зомби автоматически исчезнут, когда выйдет main.py, поэтому это серьезная проблема, если main.py работает очень долго и / или может создать много подпроцессов.) И, наконец, если вы не хотите, чтобы зомби но вы также не хотите решать, где делать ожидание (это может быть целесообразно, если оба процесса будут выполняться в течение долгого и непредсказуемого времени после этого), используйте библиотеку python-daemon, чтобы подчиненное устройство отсоединилось от ведущего устройства В этом случае вы можете продолжать использовать subprocess.call
в мастере.
Для Python 3.8.x
import shlex
import subprocess
cmd = "<full filepath plus arguments of child process>"
cmds = shlex.split(cmd)
p = subprocess.Popen(cmds, start_new_session=True)
Это позволит родительскому процессу выйти, а дочерний процесс продолжит работу. Не уверен насчет зомби.
Протестировано на Python 3.8.1 в macOS 10.15.5
Самым простым решением для вашей неблокирующей ситуации было бы добавить &
в концеPopen
так:
subprocess.Popen(["python", "slave.py", " &"])
Это не блокирует выполнение остальной части программы.
Если вы хотите запустить функцию несколько раз с разными аргументами неблокирующим образом, вы можете использовать метод ThreadPoolExecuter.
Вы отправляете свои вызовы функций исполнителю, как это
from concurrent.futures import ThreadPoolExecutor
def threadmap(fun, xs):
with ThreadPoolExecutor(max_workers=8) as executer:
return list(executer.map(fun, xs))