2010-11-12 6 views
6

Estoy ejecutando un script de Perl a través del módulo de subproceso en Python en Linux. La función que ejecuta el script se llama varias veces con entrada variable.¿Por qué tengo que usar .wait() con el módulo de subproceso de python?

def script_runner(variable_input): 

    out_file = open('out_' + variable_input, 'wt') 
    error_file = open('error_' + variable_input, 'wt') 

    process = subprocess.Popen(['perl', 'script', 'options'], shell=False, 
          stdout=out_file, stderr=error_file) 

Sin embargo, si ejecuto esta función, por ejemplo, dos veces, la ejecución del primer proceso se detendrá cuando comience el segundo proceso. Puedo obtener mi comportamiento deseado agregando

process.wait() 

después de llamar al script, así que no estoy realmente atascado. Sin embargo, quiero averiguar por qué no puedo ejecutar el script usando el subproceso tantas veces como quiera, y hacer que el script haga estos cálculos en paralelo, sin tener que esperar a que termine entre cada ejecución.

ACTUALIZACIÓN

El culpable no era tan emocionante: el guión de Perl se utiliza un archivo común que fue reescrito para cada ejecución.

Sin embargo, la lección que aprendí de esto fue que el recolector de basura no elimina el proceso una vez que se inicia, porque esto no influyó en mi script una vez que lo resolvió.

+1

¿está seguro de que el segundo se completa? ¿Te refieres a la invocación * same * dos veces (la misma 'variable_input')? – knitti

+0

El segundo se completa como se esperaba. Quiero decir con diferentes 'variable_input'. Por ejemplo, 'test_1' y 'test_2'. – Viktiglemma

+0

¿Qué hay sobre tratar de mantener una referencia a ambos procesos, p. devolviendo el proceso y almacenándolo en una variable? – knitti

Respuesta

2

Si se está utilizando Unix, y desea ejecutar varios procesos en segundo plano, se puede utilizar subprocess.Popen esta manera:

x_fork_many.py:

import subprocess 
import os 
import sys 
import time 
import random 
import gc # This is just to test the hypothesis that garbage collection of p=Popen() causing the problem. 

# This spawns many (3) children in quick succession 
# and then reports as each child finishes. 
if __name__=='__main__': 
    N=3 
    if len(sys.argv)>1: 
     x=random.randint(1,10) 
     print('{p} sleeping for {x} sec'.format(p=os.getpid(),x=x)) 
     time.sleep(x) 
    else: 
     for script in xrange(N): 
      args=['test.py','sleep'] 
      p = subprocess.Popen(args) 
     gc.collect() 
     for i in range(N): 
      pid,retval=os.wait() 
      print('{p} finished'.format(p=pid)) 

La salida se ve algo como esto:

% x_fork_many.py 
15562 sleeping for 10 sec 
15563 sleeping for 5 sec 
15564 sleeping for 6 sec 
15563 finished 
15564 finished 
15562 finished 

No estoy seguro de por qué está obteniendo el comportamiento extraño al no llamar al .wait(). Sin embargo, el script anterior sugiere (al menos en Unix) que no es necesario guardar los procesos subprocess.Popen(...) en una lista o conjunto. Cualquiera que sea el problema, no creo que tenga que ver con la recolección de basura.

PS. Tal vez sus scripts Perl están en conflicto de alguna manera, lo que hace que uno termine con un error cuando se está ejecutando otro. ¿Has intentado iniciar múltiples llamadas al script de Perl desde la línea de comando?

+0

El problema era que las secuencias de comandos usaban un archivo común que se sobrescribía. Ahora ejecuto los programas en paralelo con y sin agregarlos a una lista para mantener el objeto a salvo del recolector de basura. – Viktiglemma

+0

@Viktiglemma: Ah. Me alegra que hayas resuelto el problema. – unutbu

1

Tienes que llamar a wait() para preguntar "esperar" el final de tu popen.

Como popen ejecuta en segundo plano el script de perl, si no espera(), se detendrá en el final de la vida del "proceso" del objeto ... que está al final de script_runner.

+0

En otras palabras ... Si el proceso get está fuera del alcance, será elegible para la recolección de basura. No hay forma de saber cuándo se va a * recoger realmente el objeto, pero no se debe confiar en eso. Llamada espera explícitamente. – extraneon

+1

@extraneon: Python es una referencia contada, por lo que tan pronto como nadie tenga una referencia al 'proceso', el objeto se eliminará y el subproceso se detendrá. –

+0

De todos modos, parece que incluso si almaceno el objeto en una lista, los procesos todavía están interrumpidos. – Viktiglemma

1

Como dijo por ericdupo, la tarea se mata porque se sobrescribe la variable de process con un nuevo Popen objeto, y puesto que no hay más referencias a su Popen objeto anterior, es destruido por el recolector de basura.Puede evitar esto manteniendo una referencia a los objetos en algún lugar, como una lista:

processes = [] 
def script_runner(variable_input): 

    out_file = open('out_' + variable_input, 'wt') 
    error_file = open('error_' + variable_input, 'wt') 

    process = subprocess.Popen(['perl', 'script', 'options'], shell=False, 
          stdout=out_file, stderr=error_file) 
    processes.append(process) 

Esto debería ser suficiente para evitar que su Popen objeto anterior de la destrucción

+0

Probé este código exactamente, pero el resultado sigue siendo el mismo. Creo que hay más que el recolector de basura. – Viktiglemma

+0

extraño, lo probé con un comando "lento", y funcionó bien, el problema debe estar en otro lugar entonces. Lamento que esto no resuelva tu problema. – MatToufoutu

+0

Lo siento: ¡tienes razón! El problema era que todos los scripts usaban un archivo común que se sobrescribía cada vez. Sin embargo, una vez que lo hice funcionar, no era necesario que agregara los procesos a una lista para evitar el recolector de basura. – Viktiglemma

0

creo que quiere hacer

list_process = [] 
def script_runner(variable_input): 

    out_file = open('out_' + variable_input, 'wt') 
    error_file = open('error_' + variable_input, 'wt') 

    process = subprocess.Popen(['perl', 'script', 'options'], shell=False, 
          stdout=out_file, stderr=error_file) 
    list_process.append(process) 
#call several times script_runner 
for process in list_process: 
    process.wait() 

por lo que su proceso se ejecutará en paralelo

Cuestiones relacionadas