Как закрыть канал stdout при уничтожении процесса, запущенного с помощью подпроцесса python Popen? - PullRequest
9 голосов
/ 26 октября 2009

Интересно, возможно ли отключить канал связи при уничтожении подпроцесса, запущенного в другом потоке. Если я не вызову connect (), то kill () будет работать как положено, завершив процесс через одну секунду вместо пяти.

Я нашел обсуждение подобной проблемы здесь , но я не получил реальных ответов. Я предполагаю, что я либо должен быть в состоянии закрыть канал, либо явно уничтожить подпроцесс (который в этом примере называется "сон") и уничтожить его, чтобы разблокировать канал.

Я также пытался найти ответ на нее на SO, но я нашел только this и this и this , которые напрямую не решают эту проблему как насколько я могу судить (?).

Итак, что я хочу сделать, так это уметь запускать команду во втором потоке и получать весь ее вывод, но иметь возможность немедленно убить ее, когда захочу. Я мог бы пройтись по файлу и отследить его или подобное, но я думаю, что должен быть лучший способ сделать это?

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

Ответы [ 3 ]

6 голосов
/ 13 сентября 2011

Мне кажется, проблема в том, что process.kill () убивает только непосредственный дочерний процесс (bash), а не подпроцессы сценария bash.

Проблема и решение описаны здесь:

Используйте 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
0 голосов
/ 26 октября 2009

Мне кажется, что самый простой способ сделать это и обойти проблемы многопоточности - это установить флаг уничтожения из основного потока и проверить его в потоке, выполняющем сценарий, непосредственно перед связью, убивая сценарий, флаг True.

0 голосов
/ 26 октября 2009

Похоже, вы можете стать жертвой супергрубого параллелизма 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

Короче говоря, ваш код работает как логически реализованный. :)

...