Python в Windows – как ждать нескольких дочерних процессов?

Как подождать несколько дочерних процессов в Python в Windows, без активного ожидания (опроса)? Что-то вроде этого почти работает для меня:

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

Проблема в том, что когда proc2 заканчивается до proc1 , родительский процесс все равно будет ждать proc1 . В Unix можно было бы использовать waitpid(0) в цикле, чтобы получить коды возврата дочерних процессов по мере их завершения – как добиться чего-то подобного в Python в Windows?

Это может показаться излишним, но, вот оно:

 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 

Twisted имеет асинхронный API-интерфейс, который работает в Windows. На самом деле существует несколько различных реализаций, многие из которых не так велики, но вы можете переключаться между ними, не меняя свой код.

Основываясь на ответе zseil, вы можете сделать это с помощью сочетания подпроцессов и вызовов API win32. Я использовал прямые ctypes, потому что у моего Python не установлено win32api. Я просто создаю sleep.exe из MSYS здесь в качестве примера, но, несомненно, вы можете создать любой процесс, который вам нравится. Я использую OpenProcess (), чтобы получить HANDLE из PID процесса, а затем WaitForMultipleObjects, чтобы дождаться завершения любого процесса.

 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!" 

Twisted on Windows будет выполнять ожидание под обложками. Если вы не хотите использовать потоки, вам придется использовать win32 API, чтобы избежать опроса. Что-то вроде этого:

 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) 

Вы можете использовать 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) 

«нет» и «живые» – это списки, указывающие, какие процессы исчезли и какие из них все еще живы.

При желании вы можете указать обратный вызов, который вызывается каждый раз, когда один из отслеживаемых процессов завершается:

 >>> def on_terminate(proc): ... print "%s terminated" % proc ... >>> gone, alive = psutil.wait_procs(ls, timeout=3, callback=on_terminate)