¿Cómo iniciar un proceso en segundo plano en Python?


295

Estoy tratando de portar un script de shell a la versión de Python mucho más legible. El script de shell original inicia varios procesos (utilidades, monitores, etc.) en segundo plano con "&". ¿Cómo puedo lograr el mismo efecto en Python? Me gustaría que estos procesos no mueran cuando se completen los scripts de Python. Estoy seguro de que está relacionado con el concepto de demonio de alguna manera, pero no pude encontrar cómo hacerlo fácilmente.


1
La pregunta realmente duplicada es ¿Cómo iniciar y ejecutar un script externo en segundo plano? . Saludos;)
olibre

1
Hola artem Acepte la respuesta de Dan porque (1) más votos, (2) subprocess.Popen()es la nueva forma recomendada desde 2010 (estamos en 2015 ahora) y (3) la pregunta duplicada que redirecciona aquí también tiene una respuesta aceptada subprocess.Popen(). Saludos :-)
olibre

1
@olibre De hecho, la respuesta debería ser subprocess.Popen("<command>")con el archivo <command> dirigido por un shebang adecuado. Funciona perfecto para mí (Debian) con scripts de bash y python, implícitamente, shelly sobrevive a su proceso padre. stdoutva a la misma terminal que la de los padres. Entonces, esto funciona de manera muy similar &a un shell que era una solicitud de OP. Pero el infierno, todas las preguntas se resuelven muy complejo, mientras que un poco de las pruebas mostraron que en ningún momento;)
flaschbier

Para conocer los antecedentes, consulte también stackoverflow.com/a/51950538/874188
tripleee

Respuestas:


84

Nota : esta respuesta es menos actual que cuando se publicó en 2009. subprocessAhora se recomienda usar el módulo que se muestra en otras respuestas en los documentos

(Tenga en cuenta que el módulo de subproceso proporciona recursos más potentes para generar nuevos procesos y recuperar sus resultados; es preferible usar ese módulo que usar estas funciones).


Si desea que su proceso comience en segundo plano, puede usarlo system()y llamarlo de la misma manera que lo hizo su script de shell, o puede spawnhacerlo:

import os
os.spawnl(os.P_DETACH, 'some_long_running_command')

(o, alternativamente, puede probar la os.P_NOWAITbandera menos portátil ).

Vea la documentación aquí .


8
Observación: debe especificar la ruta completa al ejecutable. Esta función no usará la variable PATH y la variante que la usa no está disponible en Windows.
sorin

36
directamente se bloquea Python para mí.
pablo

29
os.P_DETACH ha sido reemplazado por os.P_NOWAIT.
uno mismo

77
¿Podrían las personas que sugieran el uso subprocessdarnos una pista sobre cómo separar un proceso subprocess?
rakslice

3
¿Cómo puedo usar el script Python (por ejemplo, attach.py) para encontrar un proceso en segundo plano y redirigir su IO para que attach.py ​​pueda leer / escribir en some_long_running_prog en segundo plano?
raof01

376

Si bien la solución de jkp funciona, la forma más nueva de hacer las cosas (y la forma en que lo recomienda la documentación) es usar el subprocessmódulo. Para comandos simples es equivalente, pero ofrece más opciones si desea hacer algo complicado.

Ejemplo para su caso:

import subprocess
subprocess.Popen(["rm","-r","some.file"])

Esto se ejecutará rm -r some.fileen segundo plano. Tenga en cuenta que invocar .communicate()el objeto devuelto desde Popense bloqueará hasta que se complete, así que no lo haga si desea que se ejecute en segundo plano:

import subprocess
ls_output=subprocess.Popen(["sleep", "30"])
ls_output.communicate()  # Will block for 30 seconds

Mira la documentación aquí .

Además, un punto de aclaración: "Fondo" tal como lo usa aquí es puramente un concepto de shell; técnicamente, lo que quieres decir es que quieres generar un proceso sin bloquear mientras esperas a que se complete. Sin embargo, he usado "fondo" aquí para referirme al comportamiento similar a un fondo de shell.


77
@Dan: ¿Cómo elimino el proceso una vez que se ejecuta en segundo plano? Quiero ejecutarlo por un tiempo (es un demonio con el que interactúo) y matarlo cuando termine. Los documentos no son útiles ...
Juan

1
@ Dan, pero ¿no necesito saber el PID para eso? El monitor de actividad / administrador de tareas no es una opción (debe suceder mediante programación).
Juan

55
ok, entonces, ¿cómo fuerza el proceso a un segundo plano cuando necesita el resultado de Popen () para escribir en su stdin?
Michael

2
@JFSebastian: Lo interpreté como "cómo puedo crear un proceso independiente que no detenga la ejecución del programa actual". ¿Cómo sugeriría que lo edite para hacerlo más explícito?
Dan

1
@Dan: la respuesta correcta es: use Popen()para evitar bloquear el hilo principal y si necesita un demonio, mire el python-daemonpaquete para comprender cómo debe comportarse un demonio bien definido. Su respuesta está bien si elimina todo lo que comienza con "Pero tenga cuidado", excepto el enlace a los documentos del subproceso.
jfs

47

Probablemente desee la respuesta a "Cómo llamar a un comando externo en Python" .

El enfoque más simple es usar la os.systemfunción, por ejemplo:

import os
os.system("some_command &")

Básicamente, lo que pase a la systemfunción se ejecutará de la misma manera que si lo hubiera pasado al shell en un script.


10
En mi humilde opinión, los scripts de Python generalmente se escriben para ser multiplataforma y si existe una solución multiplataforma simple, es mejor quedarse con ella. Nunca se sabe si tendrá que trabajar con otra plataforma en el futuro :) O si algún otro hombre querría migrar su script a su plataforma.
d9k

77
Este comando es sincrónico (es decir, siempre espera la finalización del proceso iniciado).
tav

@ d9k ¿os.system no es portátil?
lucid_dreamer

1
@ d9k ¿no es la elección de ejecutar algo en segundo plano que ya te posiciona en posix-land? ¿Qué harías en Windows? Ejecutar como un servicio?
lucid_dreamer

¿Cómo puedo usar esto si necesito ejecutar un comando desde una carpeta específica?
MrRobot

29

Encontré esto aquí :

En Windows (win xp), el proceso padre no finalizará hasta que longtask.pyhaya finalizado su trabajo. No es lo que quieres en el script CGI. El problema no es específico de Python, en la comunidad PHP los problemas son los mismos.

La solución es pasar el DETACHED_PROCESS Indicador de creación de proceso a la CreateProcessfunción subyacente en win API. Si tiene instalado pywin32, puede importar el indicador desde el módulo de proceso win32, de lo contrario, debe definirlo usted mismo:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid

66
+1 para mostrar cómo conservar la identificación del proceso. Y si alguien quiere matar el programa más tarde con la identificación del proceso: stackoverflow.com/questions/17856928/…
iChux

1
Esto parece solo Windows
Audrius Meskauskas

24

Úselo subprocess.Popen()con el close_fds=Trueparámetro, que permitirá que el subproceso generado se separe del proceso de Python y continúe ejecutándose incluso después de que Python salga.

https://gist.github.com/yinjimmy/d6ad0742d03d54518e9f

import os, time, sys, subprocess

if len(sys.argv) == 2:
    time.sleep(5)
    print 'track end'
    if sys.platform == 'darwin':
        subprocess.Popen(['say', 'hello'])
else:
    print 'main begin'
    subprocess.Popen(['python', os.path.realpath(__file__), '0'], close_fds=True)
    print 'main end'

1
En Windows, no se separa, pero el uso del parámetro creationflags funciona
SmartManoj

3
Esta solución deja un subproceso como Zombie en Linux.
TitanFighter

@TitanFighter esto se puede evitar configurando SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009

gracias @ Jimmy tu respuesta es la ÚNICA solución que funciona para mí.
sailfish009

12

Probablemente quiera comenzar a investigar el módulo os para bifurcar diferentes hilos (abriendo una sesión interactiva y emitiendo ayuda (os)). Las funciones relevantes son fork y cualquiera de las ejecutivas. Para darle una idea de cómo comenzar, coloque algo como esto en una función que realice la bifurcación (la función debe tomar una lista o tupla 'args' como argumento que contiene el nombre del programa y sus parámetros; también puede querer para definir stdin, out y err para el nuevo hilo):

try:
    pid = os.fork()
except OSError, e:
    ## some debug output
    sys.exit(1)
if pid == 0:
    ## eventually use os.putenv(..) to set environment variables
    ## os.execv strips of args[0] for the arguments
    os.execv(args[0], args)

2
os.fork()es realmente útil, pero tiene una desventaja notable de solo estar disponible en * nix.
Evan Fosmark

El único problema con os.fork es que es específico de win32.
jkp

Más detalles sobre este enfoque: Crear un demonio al estilo Python
Amir Ali Akbari

También puede alcanzar efectos similares con threading: stackoverflow.com/a/53751896/895245 Creo que eso podría funcionar en Windows.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

9

Ambos capturan la salida y se ejecutan en segundo plano con threading

Como se menciona en esta respuesta , si captura la salida con stdout=y luego intenta hacerlo read(), el proceso se bloquea.

Sin embargo, hay casos en los que necesita esto. Por ejemplo, quería lanzar dos procesos que hablan sobre un puerto entre ellos y guardar su stdout en un archivo de registro y stdout.

El threadingmódulo nos permite hacer eso.

Primero, eche un vistazo a cómo hacer la parte de redirección de salida solo en esta pregunta: Python Popen: escriba en stdout y archivo de registro simultáneamente

Luego:

main.py

#!/usr/bin/env python3

import os
import subprocess
import sys
import threading

def output_reader(proc, file):
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            file.buffer.write(byte)
        else:
            break

with subprocess.Popen(['./sleep.py', '0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc1, \
     subprocess.Popen(['./sleep.py', '10'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc2, \
     open('log1.log', 'w') as file1, \
     open('log2.log', 'w') as file2:
    t1 = threading.Thread(target=output_reader, args=(proc1, file1))
    t2 = threading.Thread(target=output_reader, args=(proc2, file2))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

sleep.py

#!/usr/bin/env python3

import sys
import time

for i in range(4):
    print(i + int(sys.argv[1]))
    sys.stdout.flush()
    time.sleep(0.5)

Despues de correr:

./main.py

stdout se actualiza cada 0.5 segundos por cada dos líneas que contienen:

0
10
1
11
2
12
3
13

y cada archivo de registro contiene el registro respectivo para un proceso determinado.

Inspirado por: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/

Probado en Ubuntu 18.04, Python 3.6.7.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.