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
risposta
È 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()
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
@ 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
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 . –
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.
Faccio 'p = subprocess.Popen (...)' e quindi 'print p.communicate() [0] 'diverse volte. Ma 'communicate()' aspetta solo prima che il processo finisca. – sashab
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
Probabilmente è necessario esaminare il modulo [select] (http://docs.python.org/library/select.html) per attendere contemporaneamente molti sottoprocessi. – zigg
È 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()
possibile duplicato di [come eseguire diversi eseguibili utilizzando python?] (Http://stackoverflow.com/questions/9724499/how-to-run-several-executable-using-python) –
correlati: Ecco come [ eseguire più comandi shell (e facoltativamente acquisire il loro output) contemporaneamente] (http://stackoverflow.com/a/23616229/4279) – jfs