Как закрыть канал stdout-pipe при завершении процесса, запущенного с помощью подпроцесса Python Popen?
-
06-07-2019 - |
Вопрос
Интересно, можно ли отключить канал связи при завершении подпроцесса, запущенного в другом потоке?Если я не вызываю метод communication(), то kill() будет работать как положено, завершая процесс через одну секунду вместо пяти.
нашел обсуждение похожей проблемы здесь, но реальных ответов я не получил.Я предполагаю, что мне нужно либо закрыть канал, либо явно завершить подпроцесс (в данном примере это «сон») и убить его, чтобы разблокировать канал.
Я тоже пытался найти ответ ей на ТАК, но нашел только этот и этот и этот, которые, насколько я могу судить (?), не решают эту проблему напрямую.
Итак, я хочу иметь возможность запускать команду во втором потоке и получать весь ее вывод, но иметь возможность мгновенно уничтожить ее, когда я того пожелаю.Я мог бы пройти через файл и отслеживать его или что-то подобное, но я думаю, что должен быть лучший способ сделать это?
import subprocess, time
from threading import Thread
process = None
def executeCommand(command, runCommand):
Thread(target=runCommand, args=(command,)).start()
def runCommand(command):
global process
args = command.strip().split()
process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE)
for line in process.communicate():
if line:
print "process:", line,
if __name__ == '__main__':
executeCommand("./ascript.sh", runCommand)
time.sleep(1)
process.kill()
Это сценарий:
#!/bin/bash
echo "sleeping five"
sleep 5
echo "slept five"
Выход
$ time python poc.py
process: sleeping five
real 0m5.053s
user 0m0.044s
sys 0m0.000s
Решение
Я думаю, проблема в том, что процесс.kill() убивает только непосредственный дочерний процесс (bash), а не подпроцессы сценария bash.
Проблема и решение описаны здесь:
- http://www.doughellmann.com/PyMOTW/subprocess/#process-groups-sessions
- Как завершить подпроцесс Python, запущенный с помощью оболочки = True
Используйте Popen(..., preexec_fn=os.setsid) для создания группы процессов и os.pgkill для уничтожения всей группы процессов.например
import os
import signal
import subprocess
import time
from threading import Thread
process = None
def executeCommand(command, runCommand):
Thread(target=runCommand, args=(command,)).start()
def runCommand(command):
global process
args = command.strip().split()
process = subprocess.Popen(
args, shell=False, stdout=subprocess.PIPE, preexec_fn=os.setsid)
for line in process.communicate():
if line:
print "process:", line,
if __name__ == '__main__':
executeCommand("./ascript.sh", runCommand)
time.sleep(1)
os.killpg(process.pid, signal.SIGKILL)
$ time python poc.py
process: sleeping five
real 0m1.051s
user 0m0.032s
sys 0m0.020s
Другие советы
Мне кажется, что самый простой способ сделать это и обойти проблемы многопоточности - это установить флаг уничтожения из основного потока и проверить его в потоке выполнения сценария непосредственно перед связью, убивая сценарий, когда флаг установлен. True
.
Похоже, вы стали жертвой сверхгрубого параллелизма в Python.Измените свой скрипт на это:
#!/bin/bash
echo "sleeping five"
sleep 5
echo "sleeping five again"
sleep 5
echo "slept five"
И тогда вывод становится:
process: sleeping five
real 0m5.134s
user 0m0.000s
sys 0m0.010s
Если бы весь сценарий выполнялся, время было бы 10 секунд.Итак, похоже, что поток управления Python на самом деле не запускается до тех пор, пока скрипт bash не перейдет в режим сна.Аналогично, если вы измените свой сценарий на этот:
#!/bin/bash
echo "sleeping five"
sleep 1
sleep 1
sleep 1
sleep 1
sleep 1
echo "slept five"
Тогда вывод становится:
process: sleeping five
real 0m1.150s
user 0m0.010s
sys 0m0.020s
Короче говоря, ваш код работает как логически реализованный.:)