2012-03-16 5 views
10

Desidero eseguire più processi in parallelo con la possibilità di eseguire stdout in qualsiasi momento. Come dovrei farlo? Devo eseguire thread per ogni chiamata subprocess.Popen(), cosa?Sottoprocesso Python in parallelo

+0

possibile duplicato di [come eseguire diversi eseguibili utilizzando python?] (Http://stackoverflow.com/questions/9724499/how-to-run-several-executable-using-python) –

+0

correlati: Ecco come [ eseguire più comandi shell (e facoltativamente acquisire il loro output) contemporaneamente] (http://stackoverflow.com/a/23616229/4279) – jfs

risposta

13

È possibile farlo in una singola discussione.

Supponiamo di avere uno script che stampa le linee a volte casuali:

#!/usr/bin/env python 
#file: child.py 
import os 
import random 
import sys 
import time 

for i in range(10): 
    print("%2d %s %s" % (int(sys.argv[1]), os.getpid(), i)) 
    sys.stdout.flush() 
    time.sleep(random.random()) 

e desideri per raccogliere l'uscita non appena sarà disponibile, è possibile utilizzare select su sistemi POSIX come @zigg suggested:

#!/usr/bin/env python 
from __future__ import print_function 
from select  import select 
from subprocess import Popen, PIPE 

# start several subprocesses 
processes = [Popen(['./child.py', str(i)], stdout=PIPE, 
        bufsize=1, close_fds=True, 
        universal_newlines=True) 
      for i in range(5)] 

# read output 
timeout = 0.1 # seconds 
while processes: 
    # remove finished processes from the list (O(N**2)) 
    for p in processes[:]: 
     if p.poll() is not None: # process ended 
      print(p.stdout.read(), end='') # read the rest 
      p.stdout.close() 
      processes.remove(p) 

    # wait until there is something to read 
    rlist = select([p.stdout for p in processes], [],[], timeout)[0] 

    # read a line from each process that has output ready 
    for f in rlist: 
     print(f.readline(), end='') #NOTE: it can block 

Una soluzione più portatile (che dovrebbe funzionare su Windows, Linux, OSX) possono utilizzare le discussioni lettore per ogni processo, vedere Non-blocking read on a subprocess.PIPE in python.

Ecco os.pipe() soluzione basata su che funziona su Unix e Windows:

#!/usr/bin/env python 
from __future__ import print_function 
import io 
import os 
import sys 
from subprocess import Popen 

ON_POSIX = 'posix' in sys.builtin_module_names 

# create a pipe to get data 
input_fd, output_fd = os.pipe() 

# start several subprocesses 
processes = [Popen([sys.executable, 'child.py', str(i)], stdout=output_fd, 
        close_fds=ON_POSIX) # close input_fd in children 
      for i in range(5)] 
os.close(output_fd) # close unused end of the pipe 

# read output line by line as soon as it is available 
with io.open(input_fd, 'r', buffering=1) as file: 
    for line in file: 
     print(line, end='') 
# 
for p in processes: 
    p.wait() 
+2

Sembra che tu abbia multiplexato tutti gli stdout di bambini in un unico file fd (output_fd) nell'ultima soluzione. Cosa succede se 2 bambini stampano allo stesso tempo, questo non incasinerà l'output (ad esempio 'AAA \ n' + 'BBB \ n' -> 'ABBB \ nAA \ n') – dan3

+1

@ dan3: È un problema valido . 'write's che sono minori di' PIPE_BUF' byte sono atomici. In caso contrario, i dati provenienti da più processi potrebbero essere intercalati. POSIX richiede almeno 512 byte. Su Linux, 'PIPE_BUF' è 4096 byte. – jfs

+0

Ecco una domanda simile che ho postato di recente qui, http://stackoverflow.com/questions/36624056/running-a-secondary-script-in-a-new-terminal sarebbe fantastico se potesse aiutare, grazie in ogni caso . –

4

Non è necessario eseguire un thread per ogni processo. È possibile dare un'occhiata agli stream stdout per ogni processo senza bloccarli e leggerli solo se sono disponibili dati da leggere.

Si do fare attenzione a non bloccarsi accidentalmente su di essi, però, se non si intende.

+0

Faccio 'p = subprocess.Popen (...)' e quindi 'print p.communicate() [0] 'diverse volte. Ma 'communicate()' aspetta solo prima che il processo finisca. – sashab

+1

Sì, ed è per questo che non puoi usare 'communicate()' se vuoi usare un singolo thread. Esistono altri modi per ottenere stdout oltre a 'communicate()'. – Amber

+2

Probabilmente è necessario esaminare il modulo [select] (http://docs.python.org/library/select.html) per attendere contemporaneamente molti sottoprocessi. – zigg

6

È possibile anche raccogliere stdout da più sottoprocessi in concomitanza con twisted:

#!/usr/bin/env python 
import sys 
from twisted.internet import protocol, reactor 

class ProcessProtocol(protocol.ProcessProtocol): 
    def outReceived(self, data): 
     print data, # received chunk of stdout from child 

    def processEnded(self, status): 
     global nprocesses 
     nprocesses -= 1 
     if nprocesses == 0: # all processes ended 
      reactor.stop() 

# start subprocesses 
nprocesses = 5 
for _ in xrange(nprocesses): 
    reactor.spawnProcess(ProcessProtocol(), sys.executable, 
         args=[sys.executable, 'child.py'], 
         usePTY=True) # can change how child buffers stdout 
reactor.run() 

Vedi Using Processes in Twisted.