El script de Python se cuelga cuando se ejecuta en segundo plano

Tengo un script de Python (ejecutado en 2.7) que se comporta de manera diferente cuando lo ejecuto desde la línea de comandos en comparación con el fondo. Cuando lo ejecuto desde el terminal, se ejecuta como se esperaba, los dos subprocesos se ejecutan como demonios que escriben la salida en la ventana mientras el bucle principal espera el comando para salir. Se ejecuta para siempre hasta que entre en dejar de fumar:

python test.py 

Cuando el mismo progtwig se ejecuta en segundo plano, ambos subprocesos se ejecutan una vez y luego el progtwig se cuelga (lo he reducido a la entrada_de_protección, supongo que hice un supuesto incorrecto de que los dos subprocesos continuarán, incluso si se ejecutan en el background y raw_input bloquearon el principal. Por ejemplo, los dos subprocesos se ejecutarían para siempre ya que no hay entrada en este escenario.

 python test.py & 

Mi objective es tener un progtwig con esos bucles en ejecución (potencialmente para siempre) pero si lo ejecutara desde el terminal aceptaría la entrada.

Para permitir que el progtwig se ejecute desde el terminal / en segundo plano, ¿tengo que poner básicamente una instrucción if antes de la entrada_de_incrustación que verifica si está en segundo plano o no, o me falta algo que ayude?

 import sys import time from threading import Thread def threadOne(): while True: print("Thread 1") time.sleep(1) def threadTwo(): while True: print("Thread 2") time.sleep(1) # Run the threads in the background as daemons threadOne = Thread(target = threadOne) threadOne.daemon = True threadOne.start() threadTwo = Thread(target = threadTwo) threadTwo.daemon = True threadTwo.start() # Main input loop. This will allow us to enter input. The # threads will run forever unless "quit" is entered. This # doesn't run when the program is run in the background (I # incorrectly assumed it would just run forever with no input # ever being entered in that scenario). while True: userInput = "" userInput = raw_input("") time.sleep(1) # This should allow us to exit out if str(userInput) == "quit": sys.exit() 

Para permitir que el progtwig se ejecute desde el terminal / en segundo plano, ¿tengo que poner básicamente una instrucción if antes de la entrada_de_incrustación que verifica si está en segundo plano o no, o me falta algo que ayude?

De alguna manera, esto probablemente funcione (supongo que está ejecutando esto en un * nix), sin embargo, si el usuario enviara el proceso de respaldo a segundo plano (es decir, suspenda usando Ctrl Z y luego lo raw_input en segundo plano con %& ) mientras que raw_input es a la espera de la entrada del usuario, la lectura en la stdin se bloqueará como está en segundo plano, lo que hará que el kernel detenga el proceso, ya que así es como funciona stdio. Si esto es aceptable (básicamente, el usuario debe presionar Intro antes de suspender el proceso), simplemente puede hacer esto:

 import os while True: userInput = "" if os.getpgrp() == os.tcgetpgrp(sys.stdout.fileno()): userInput = raw_input("") time.sleep(1) 

Lo que hace os.getpgrp es que devuelve el ID del grupo de os actual, y luego os.tcgetpgrp obtiene el grupo de procesos asociado con la os.tcgetpgrp para este proceso, si coinciden, significa que este proceso se encuentra actualmente en primer plano, lo que significa que puede Probablemente llame a raw_input sin bloquear los hilos.

Otra pregunta planteó un problema similar y tengo una explicación más larga en: Congelar stdin cuando esté en segundo plano, descongelarlo cuando esté en primer plano .


La mejor manera es combinar esto con select.poll , y abordar la E / S interactiva por separado (usando directamente /dev/tty ) desde la E / S estándar, ya que no quiere que la redirección de stdin / stdout sea “contaminada” por eso. Aquí está la versión más completa que contiene estas dos ideas:

 tty_in = open('/dev/tty', 'r') tty_out = open('/dev/tty', 'w') fn = tty_in.fileno() poll = select.poll() poll.register(fn, select.POLLIN) while True: if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10): # 10 ms # poll should only return if the input buffer is filled, # which is triggered when a user enters a complete line, # which lets the following readline call to not block on # a lack of input. userInput = tty_in.readline() # This should allow us to exit out if userInput.strip() == "quit": sys.exit() 

La detección de fondo / primer plano aún es necesaria ya que el proceso no se separa completamente de la shell (ya que se puede volver a poner en primer plano), por lo tanto, la poll devolverá el fileno del tty si alguna entrada se envía a la shell, y si esto fileno activa el readline que luego detendrá el proceso.

Esta solución tiene la ventaja de no requerir que el usuario raw_input Intro y suspenda rápidamente la tarea para enviarla de nuevo al fondo antes de que raw_input atrape y bloquee la stdin para detener el proceso (mientras la poll verifica si hay entrada para leer), y permitir La redirección stdin / stdout (como toda la entrada interactiva se maneja a través de /dev/tty ) para que los usuarios puedan hacer algo como:

 $ python script.py < script.py 2> stderr input stream length: 2116 

En el ejemplo completado a continuación, también proporciona un aviso al usuario, es decir, se muestra un > cada vez que se envía un comando o cuando el proceso vuelve al primer plano, y se envuelve todo en una función main , y se modifica el segundo hilo para escupir cosas fuera en stderr:

 import os import select import sys import time from threading import Thread def threadOne(): while True: print("Thread 1") time.sleep(1) def threadTwo(): while True: # python 2 print does not support file argument like python 3, # so writing to sys.stderr directly to simulate error message. sys.stderr.write("Thread 2\n") time.sleep(1) # Run the threads in the background threadOne = Thread(target = threadOne) threadOne.daemon = True threadTwo = Thread(target = threadTwo) threadTwo.daemon = True def main(): threadOne.start() threadTwo.start() tty_in = open('/dev/tty', 'r') tty_out = open('/dev/tty', 'w') fn = tty_in.fileno() poll = select.poll() poll.register(fn, select.POLLIN) userInput = "" chars = [] prompt = True while True: if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10): # 10 ms # poll should only return if the input buffer is filled, # which is triggered when a user enters a complete line, # which lets the following readline call to not block on # a lack of input. userInput = tty_in.readline() # This should allow us to exit out if userInput.strip() == "quit": sys.exit() # alternatively an empty string from Ctrl-D could be the # other exit method. else: tty_out.write("user input: %s\n" % userInput) prompt = True elif not os.getpgrp() == os.tcgetpgrp(fn): time.sleep(0.1) if os.getpgrp() == os.tcgetpgrp(fn): # back to foreground, print a prompt: prompt = True if prompt: tty_out.write('> ') tty_out.flush() prompt = False if __name__ == '__main__': try: # Uncomment if you are expecting stdin # print('input stream length: %d ' % len(sys.stdin.read())) main() except KeyboardInterrupt: print("Forcibly interrupted. Quitting") sys.exit() # maybe with an error code 

Ha sido un ejercicio interesante; Esta fue una pregunta bastante buena e interesante, si puedo decir.

Una nota final: esto no es multiplataforma, no funcionará en Windows ya que no tiene select.poll y /dev/tty .