Pergunta

Como esperar por vários processos filhos em Python no Windows, sem espera ativa (polling)? Algo como isto quase funciona para mim:

proc1 = subprocess.Popen(['python','mytest.py'])
proc2 = subprocess.Popen(['python','mytest.py'])    
proc1.wait()
print "1 finished"
proc2.wait()
print "2 finished"

O problema é que quando acabamentos proc2 antes proc1, o processo pai ainda vai esperar por proc1. No Unix pode-se usar waitpid(0) em um loop para obter os códigos de devolver a criança processos, como eles terminar? - como conseguir algo parecido com isso em Python no Windows

Foi útil?

Solução

Pode parecer um exagero, mas, aqui vai:

import Queue, thread, subprocess

results= Queue.Queue()
def process_waiter(popen, description, que):
    try: popen.wait()
    finally: que.put( (description, popen.returncode) )
process_count= 0

proc1= subprocess.Popen( ['python', 'mytest.py'] )
thread.start_new_thread(process_waiter,
    (proc1, "1 finished", results))
process_count+= 1

proc2= subprocess.Popen( ['python', 'mytest.py'] )
thread.start_new_thread(process_waiter,
    (proc2, "2 finished", results))
process_count+= 1

# etc

while process_count > 0:
    description, rc= results.get()
    print "job", description, "ended with rc =", rc
    process_count-= 1

Outras dicas

torcida tem um assíncrono API que desova processo , que funciona em Windows. Na verdade, existem várias implementações diferentes, muitos dos quais não são tão grande, mas você pode alternar entre eles sem alterar seu código.

Com base na resposta de zseil, você pode fazer isso com uma mistura de subprocesso e chamadas de API Win32. Eu costumava ctypes retas, porque meu Python não acontecer de ter win32api instalado. Estou apenas desova Sleep.exe de MSYS aqui como um exemplo, mas é evidente que você poderia gerar qualquer processo você gosta. Eu uso OpenProcess () para obter uma alça do processo PID, e depois WaitForMultipleObjects de esperar por qualquer processo ao fim.

import ctypes, subprocess
from random import randint
SYNCHRONIZE=0x00100000
INFINITE = -1
numprocs = 5
handles = {}

for i in xrange(numprocs):
    sleeptime = randint(5,10)
    p = subprocess.Popen([r"c:\msys\1.0\bin\sleep.exe", str(sleeptime)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
    h = ctypes.windll.kernel32.OpenProcess(SYNCHRONIZE, False, p.pid)
    handles[h] = p.pid
    print "Spawned Process %d" % p.pid

while len(handles) > 0:
    print "Waiting for %d children..." % len(handles)
    arrtype = ctypes.c_long * len(handles)
    handle_array = arrtype(*handles.keys())
    ret = ctypes.windll.kernel32.WaitForMultipleObjects(len(handle_array), handle_array, False, INFINITE)
    h = handle_array[ret]
    ctypes.windll.kernel32.CloseHandle(h)
    print "Process %d done" % handles[h]
    del handles[h]
print "All done!"

torcida no Windows irá realizar uma espera ativa debaixo das cobertas. Se você não quiser usar threads, você terá que usar a API Win32 para polling evitar. Algo parecido com isto:

import win32process
import win32event

# Note: CreateProcess() args are somewhat cryptic, look them up on MSDN
proc1, thread1, pid1, tid1 = win32process.CreateProcess(...)
proc2, thread2, pid2, tid2 = win32process.CreateProcess(...)
thread1.close()
thread2.close()

processes = {proc1: "proc1", proc2: "proc2"}

while processes:
    handles = processes.keys()
    # Note: WaitForMultipleObjects() supports at most 64 processes at a time
    index = win32event.WaitForMultipleObjects(handles, False, win32event.INFINITE)
    finished = handles[index]
    exitcode = win32process.GetExitCodeProcess(finished)
    procname = processes.pop(finished)
    finished.close()
    print "Subprocess %s finished with exit code %d" % (procname, exitcode)

Você pode usar psutil :

>>> import subprocess
>>> import psutil
>>> 
>>> proc1 = subprocess.Popen(['python','mytest.py'])
>>> proc2 = subprocess.Popen(['python','mytest.py'])    
>>> ls = [psutil.Process(proc1.pid), psutil.Process(proc2.pid)]
>>>
>>> gone, alive = psutil.wait_procs(ls, timeout=3)

'Gone' e 'vivo' são listas que indicam quais os processos se foram e quais os que ainda estão vivos.

Opcionalmente, você pode especificar um callback que é invocado sempre que um dos termina processos assistidos:

>>> def on_terminate(proc):
...     print "%s terminated" % proc
...
>>> gone, alive = psutil.wait_procs(ls, timeout=3, callback=on_terminate)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top