Come avviare un processo in background in Python?


295

Sto cercando di eseguire il porting di uno script di shell sulla versione di Python molto più leggibile. Lo script shell originale avvia diversi processi (utility, monitor, ecc.) In background con "&". Come posso ottenere lo stesso effetto in Python? Vorrei che questi processi non morissero quando gli script di Python sono completi. Sono sicuro che sia in qualche modo legato al concetto di demone, ma non sono riuscito a trovare facilmente questo.


1
La domanda davvero duplicata è: come avviare ed eseguire script esterni in background? . Saluti;)
olibre

1
Ciao Artem Accetta la risposta di Dan perché (1) più voti, (2) subprocess.Popen()è il nuovo modo raccomandato dal 2010 (siamo nel 2015 ora) e (3) la duplice domanda che reindirizza qui ha anche una risposta accettata subprocess.Popen(). Saluti :-)
olibre,

1
@olibre In effetti la risposta dovrebbe essere subprocess.Popen("<command>")con il file <command> guidato da un shebang adatto. Funziona perfettamente per me (Debian) con script bash e python, implicitamente se shellsopravvive al suo processo genitore. stdoutva allo stesso terminale di quello del genitore. Quindi funziona in modo molto simile &a una shell che era una richiesta di OP. Ma diavolo, tutte le domande funzionano in modo molto complesso mentre un piccolo test lo ha mostrato in pochissimo tempo;)
flaschbier,

Per sfondo magari vedere anche stackoverflow.com/a/51950538/874188
tripleee

Risposte:


84

Nota : questa risposta è meno attuale rispetto a quando era stata pubblicata nel 2009. L'utilizzo del subprocessmodulo mostrato in altre risposte è ora raccomandato nei documenti

(Si noti che il modulo di sottoprocesso fornisce strutture più potenti per generare nuovi processi e recuperare i loro risultati; usare quel modulo è preferibile usare queste funzioni.)


Se vuoi che il tuo processo inizi in background puoi usarlo system()e chiamarlo nello stesso modo del tuo script di shell, oppure puoi spawn:

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

(o, in alternativa, puoi provare il os.P_NOWAITflag meno portatile ).

Vedi la documentazione qui .


8
Nota: è necessario specificare il percorso completo dell'eseguibile. Questa funzione non utilizzerà la variabile PATH e la variante che la utilizza non è disponibile in Windows.
sorin,

36
dritto su schianto pitone per me.
pablo,

29
os.P_DETACH è stato sostituito con os.P_NOWAIT.
stesso

7
Le persone che suggeriscono di usarci potrebbero subprocessdarci un suggerimento su come staccare un processo subprocess?
Rakslice,

3
Come posso usare lo script Python (diciamo attach.py) per trovare un processo in background e reindirizzare il suo IO in modo che attach.py ​​possa leggere / scrivere su some_long_running_prog in background?
raof01

376

Mentre la soluzione di jkp funziona, il modo più nuovo di fare le cose (e il modo in cui la documentazione raccomanda) è usaresubprocess modulo. Per comandi semplici è equivalente, ma offre più opzioni se si desidera fare qualcosa di complicato.

Esempio per il tuo caso:

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

Questo verrà eseguito rm -r some.filein background. Si noti che la chiamata .communicate()sull'oggetto restituito da Popenverrà bloccata fino al completamento, quindi non farlo se si desidera che venga eseguito in background:

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

Vedi la documentazione qui .

Inoltre, un punto di chiarimento: "Sfondo" mentre lo usi qui è puramente un concetto di shell; tecnicamente, ciò che vuoi dire è che vuoi generare un processo senza bloccarlo mentre aspetti che venga completato. Tuttavia, ho usato "background" qui per riferirmi a comportamenti di tipo shell-background.


7
@ Dan: Come posso interrompere il processo una volta che è in esecuzione in background? Voglio eseguirlo per un po '(è un demone con cui interagisco) e ucciderlo quando avrò finito. I documenti non sono utili ...
Juan,

1
@Dan ma non ho bisogno di conoscere il PID per questo? Activity monitor / Task manager non è un'opzione (deve avvenire a livello di codice).
Juan,

5
ok, quindi come forzare il processo in background quando è necessario il risultato di Popen () per scrivere sul suo stdin?
Michael,

2
@JFSebastian: l'ho interpretato come "come posso creare un processo indipendente che non fermi l'esecuzione del programma attuale". Come suggeriresti di modificarlo per renderlo più esplicito?
Dan,

1
@Dan: la risposta corretta è: usa Popen()per evitare di bloccare il thread principale e se hai bisogno di un demone allora guarda il python-daemonpacchetto per capire come dovrebbe comportarsi un demone ben definito. La tua risposta è ok se rimuovi tutto a partire da "Ma stai attento" tranne il collegamento ai documenti dei sottoprocessi.
jfs,

47

Probabilmente vuoi la risposta a "Come chiamare un comando esterno in Python" .

L'approccio più semplice è utilizzare la os.systemfunzione, ad esempio:

import os
os.system("some_command &")

Fondamentalmente, qualunque cosa passi alla systemfunzione verrà eseguita come se la passassi alla shell in uno script.


9
IMHO, gli script Python sono generalmente scritti per essere multipiattaforma e se esiste una soluzione multipiattaforma semplice è meglio attenersi ad essa. Non so mai se in futuro dovrai lavorare con un'altra piattaforma :) O se qualche altro uomo vorrebbe migrare il tuo script sulla sua piattaforma.
d9k,

7
Questo comando è sincrono (ovvero attende sempre la fine del processo avviato).
tav

@ d9k os.system non è portatile?
lucid_dreamer,

1
@ d9k non è la scelta di eseguire qualcosa in background già posizionandoti in posix-land? Cosa faresti su Windows? Eseguire come servizio?
lucid_dreamer,

come posso usarlo se devo eseguire un comando da una cartella specifica?
sig

29

L'ho trovato qui :

Su Windows (Win XP), il processo padre non terminerà fino a quando longtask.py avrà terminato il suo lavoro. Non è quello che vuoi in CGI-script. Il problema non è specifico di Python, nella comunità PHP i problemi sono gli stessi.

La soluzione è passare il DETACHED_PROCESS Flag di creazione del processo alla CreateProcessfunzione sottostante nell'API win. Se hai installato pywin32 puoi importare il flag dal modulo win32process, altrimenti dovresti definirlo tu stesso:

DETACHED_PROCESS = 0x00000008

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

6
+1 per mostrare come conservare l'id del processo. E se qualcuno vuole uccidere il programma più tardi con l'ID del processo: stackoverflow.com/questions/17856928/...
iChux

1
Sembra solo Windows
Audrius Meskauskas

24

Utilizzare subprocess.Popen()con il close_fds=Trueparametro, che consentirà di scollegare il sottoprocesso generato dal processo Python stesso e continuare l'esecuzione anche dopo la chiusura di Python.

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
In Windows, non si stacca ma usando il parametro
createflags

3
Questa soluzione lascia un sottoprocesso come Zombie su Linux.
TitanFighter

@TitanFighter questo può essere evitano dall'insieme SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/...
sailfish009

grazie @Jimmy la tua risposta è che l'UNICA soluzione funziona per me.
sailfish009

12

Probabilmente si desidera iniziare a studiare il modulo os per il fork di thread diversi (aprendo una sessione interattiva e fornendo aiuto (os)). Le funzioni rilevanti sono fork e ognuna di esse. Per darti un'idea di come iniziare, inserisci qualcosa del genere in una funzione che esegue il fork (la funzione deve prendere un elenco o tuple 'args' come argomento che contiene il nome del programma e i suoi parametri; potresti anche voler per definire stdin, out ed err per il nuovo thread):

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()è davvero utile, ma ha un notevole svantaggio di essere disponibile solo su * nix.
Evan Fosmark,

L'unico problema con os.fork è che è specifico per win32.
jkp,

Maggiori dettagli su questo approccio: Creare un demone alla maniera di Python
Amir Ali Akbari

Puoi anche ottenere effetti simili con threading: stackoverflow.com/a/53751896/895245 Penso che potrebbe funzionare su Windows.
Ciro Santilli 12 冠状 病 六四 事件 法轮功

9

Entrambi catturano l'output e vengono eseguiti in background con threading

Come menzionato in questa risposta , se catturi l'output con stdout=e poi provi a read(), il processo si blocca.

Tuttavia, ci sono casi in cui è necessario questo. Ad esempio, volevo avviare due processi che dialogano su una porta tra loro e salvare il loro stdout in un file di registro e stdout.

Il threadingmodulo ci consente di farlo.

Innanzitutto, dai un'occhiata a come eseguire la parte di reindirizzamento dell'output da sola in questa domanda: Python Popen: Scrivi nello stdout e nel file di registro contemporaneamente

Poi:

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)

Dopo l'esecuzione:

./main.py

stdout viene aggiornato ogni 0,5 secondi per contenere ogni due righe:

0
10
1
11
2
12
3
13

e ogni file di registro contiene il rispettivo registro per un determinato processo.

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

Testato su Ubuntu 18.04, Python 3.6.7.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.