Arrêtez de lire la sortie de processus en Python sans blocage?
-
08-10-2019 - |
Question
J'ai un programme Python pour Linux ressemble presque à celui-ci:
import os
import time
process = os.popen("top").readlines()
time.sleep(1)
os.popen("killall top")
print process
programme se bloque dans cette ligne:
process = os.popen("top").readlines()
et qui se passe dans les outils qui maintiennent la mise à jour comme délivrer en sortie « Top »
mes meilleurs essais:
import os
import time
import subprocess
process = subprocess.Popen('top')
time.sleep(2)
os.popen("killall top")
print process
il a mieux fonctionné que le premier (il est kelled), mais il retourne:
<subprocess.Popen object at 0x97a50cc>
le second essai:
import os
import time
import subprocess
process = subprocess.Popen('top').readlines()
time.sleep(2)
os.popen("killall top")
print process
le même que le premier. Il est pendu à cause de "readlines ()"
Son retour devrait ressembler à ceci:
top - 05:31:15 up 12:12, 5 users, load average: 0.25, 0.14, 0.11
Tasks: 174 total, 2 running, 172 sleeping, 0 stopped, 0 zombie
Cpu(s): 9.3%us, 3.8%sy, 0.1%ni, 85.9%id, 0.9%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1992828k total, 1849456k used, 143372k free, 233048k buffers
Swap: 4602876k total, 0k used, 4602876k free, 1122780k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
31735 Barakat 20 0 246m 52m 20m S 19.4 2.7 13:54.91 totem
1907 root 20 0 91264 45m 15m S 1.9 2.3 38:54.14 Xorg
2138 Barakat 20 0 17356 5368 4284 S 1.9 0.3 3:00.15 at-spi-registry
2164 Barakat 9 -11 164m 7372 6252 S 1.9 0.4 2:54.58 pulseaudio
2394 Barakat 20 0 27212 9792 8256 S 1.9 0.5 6:01.48 multiload-apple
6498 Barakat 20 0 56364 30m 18m S 1.9 1.6 0:03.38 pyshell
1 root 20 0 2880 1416 1208 S 0.0 0.1 0:02.02 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 0:00.12 migration/0
4 root 20 0 0 0 0 S 0.0 0.0 0:02.07 ksoftirqd/0
5 root RT 0 0 0 0 S 0.0 0.0 0:00.00 watchdog/0
9 root 20 0 0 0 0 S 0.0 0.0 0:01.43 events/0
11 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuset
12 root 20 0 0 0 0 S 0.0 0.0 0:00.02 khelper
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 netns
14 root 20 0 0 0 0 S 0.0 0.0 0:00.00 async/mgr
15 root 20 0 0 0 0 S 0.0 0.0 0:00.00 pm
et enregistrer dans la variable « processus ». Tous les types d'idée de moi, je suis vraiment coincé maintenant?
La solution
#!/usr/bin/env python
"""Start process; wait 2 seconds; kill the process; print all process output."""
import subprocess
import tempfile
import time
def main():
# open temporary file (it automatically deleted when it is closed)
# `Popen` requires `f.fileno()` so `SpooledTemporaryFile` adds nothing here
f = tempfile.TemporaryFile()
# start process, redirect stdout
p = subprocess.Popen(["top"], stdout=f)
# wait 2 seconds
time.sleep(2)
# kill process
#NOTE: if it doesn't kill the process then `p.wait()` blocks forever
p.terminate()
p.wait() # wait for the process to terminate otherwise the output is garbled
# print saved output
f.seek(0) # rewind to the beginning of the file
print f.read(),
f.close()
if __name__=="__main__":
main()
Tail-like Solutions qui impriment seule la partie de la sortie
Vous pouvez lire la sortie de processus dans un autre thread et enregistrer le nombre requis des dernières lignes dans une file d'attente:
import collections
import subprocess
import time
import threading
def read_output(process, append):
for line in iter(process.stdout.readline, ""):
append(line)
def main():
# start process, redirect stdout
process = subprocess.Popen(["top"], stdout=subprocess.PIPE, close_fds=True)
try:
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(maxlen=number_of_lines) # atomic .append()
t = threading.Thread(target=read_output, args=(process, q.append))
t.daemon = True
t.start()
#
time.sleep(2)
finally:
process.terminate() #NOTE: it doesn't ensure the process termination
# print saved lines
print ''.join(q)
if __name__=="__main__":
main()
Cette variante nécessite q.append()
être opération atomique. Sinon, la sortie peut-être endommagé.
signal.alarm()
solution
Vous pouvez utiliser signal.alarm()
pour appeler le process.terminate()
après le délai spécifié au lieu de lecture dans un autre fil. Bien qu'il pourrait ne pas interagir très bien avec le module subprocess
. Sur la base de réponse Martelli @ Alex:
import collections
import signal
import subprocess
class Alarm(Exception):
pass
def alarm_handler(signum, frame):
raise Alarm
def main():
# start process, redirect stdout
process = subprocess.Popen(["top"], stdout=subprocess.PIPE, close_fds=True)
# set signal handler
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(2) # produce SIGALRM in 2 seconds
try:
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(maxlen=number_of_lines)
for line in iter(process.stdout.readline, ""):
q.append(line)
signal.alarm(0) # cancel alarm
except Alarm:
process.terminate()
finally:
# print saved lines
print ''.join(q)
if __name__=="__main__":
main()
Cette approche ne fonctionne que sur les systèmes * nix. Il pourrait bloquer si process.stdout.readline()
ne retourne pas.
threading.Timer
solution
import collections
import subprocess
import threading
def main():
# start process, redirect stdout
process = subprocess.Popen(["top"], stdout=subprocess.PIPE, close_fds=True)
# terminate process in timeout seconds
timeout = 2 # seconds
timer = threading.Timer(timeout, process.terminate)
timer.start()
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(process.stdout, maxlen=number_of_lines)
timer.cancel()
# print saved lines
print ''.join(q),
if __name__=="__main__":
main()
Cette approche devrait également fonctionner sur Windows. Ici, j'ai process.stdout
utilisé comme itérables; il pourrait introduire une mise en mémoire tampon de sortie supplémentaire, vous pouvez passer à l'approche iter(process.stdout.readline, "")
si elle n'est pas souhaitable. si le processus ne se termine pas sur process.terminate()
puis les scripts se bloque.
Pas de fils, aucune solution signaux
import collections
import subprocess
import sys
import time
def main():
args = sys.argv[1:]
if not args:
args = ['top']
# start process, redirect stdout
process = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
# save last `number_of_lines` lines of the process output
number_of_lines = 200
q = collections.deque(maxlen=number_of_lines)
timeout = 2 # seconds
now = start = time.time()
while (now - start) < timeout:
line = process.stdout.readline()
if not line:
break
q.append(line)
now = time.time()
else: # on timeout
process.terminate()
# print saved lines
print ''.join(q),
if __name__=="__main__":
main()
Cette utilisation variante ni fils, aucun signal mais brouillé produit une sortie dans le terminal. Il bloque si les blocs de process.stdout.readline()
.
Autres conseils
Au lieu d'utiliser « top » Je suggère d'utiliser « ps » qui vous donnera les mêmes informations, mais une seule fois au lieu d'une fois par seconde pour toute l'éternité.
Vous aurez besoin d'utiliser aussi des drapeaux avec ps, je tends à utiliser « ps aux »
Qu'est-ce que je ferais, plutôt que cette approche, est d'examiner le programme que vous essayez d'obtenir des informations à partir et déterminer la source de cette information ultime. Il peut être un appel API ou nœud de périphérique. Ensuite, écrire quelques python qu'il obtient de la même source. Cela permet d'éliminer les problèmes et les frais généraux de données « grattage » « cuit ».
(J. F. Sebastian vos codes ne fonctionnent pas très bien, je pense qu'il vaut mieux que ma solution =))
Je l'ai résolu à l'aide d'une autre manière.
Au lieu de faire la sortie directement sur le terminal que je fais dans un fichier « tmp_file »:
top >> tmp_file
J'ai ensuite utilisé l'outil « coupe » pour faire sa sortie « qui est sortie supérieure » comme valeur de processus
cat tmp_file
et il a fait ce que je veux faire .C'est le code final:
import os
import subprocess
import time
subprocess.Popen("top >> tmp_file",shell = True)
time.sleep(1)
os.popen("killall top")
process = os.popen("cat tmp_file").read()
os.popen("rm tmp_file")
print process
# Thing better than nothing =)
Merci les gars beaucoup pour aider
Dans les faits, si vous remplissez la mémoire tampon de sortie, vous allez finir avec une réponse. Donc, une solution consiste à remplir la mémoire tampon avec une grande quantité de déchets (~ 6000 caractères avec bufsize = 1).
Le mot Let, au lieu de haut, vous avez un script python qui écriture sur sys.stdout:
GARBAGE='.\n'
sys.stdout.write(valuable_output)
sys.stdout.write(GARBAGE*3000)
Du côté lanceur, au lieu de process.readline simple ():
GARBAGE='.\n'
line=process.readline()
while line==GARBAGE:
line=process.readline()
Bien sûr qu'il est un sale peu comme 2000 dépend de la mise en œuvre du sous-processus, mais il fonctionne très bien et est très simple. réglage de quoi que ce soit, mais bufsize = 1 aggraver la question.