Jak rozpocząć proces w tle w Pythonie?


295

Próbuję przenieść skrypt powłoki do znacznie bardziej czytelnej wersji Pythona. Oryginalny skrypt powłoki uruchamia w tle kilka procesów (programy narzędziowe, monitory itp.) Za pomocą „&”. Jak mogę osiągnąć ten sam efekt w Pythonie? Chciałbym, aby te procesy nie umarły po zakończeniu skryptów Pythona. Jestem pewien, że ma to jakoś związek z koncepcją demona, ale nie mogłem znaleźć łatwego sposobu.


1
Naprawdę zduplikowane pytanie brzmi: Jak uruchomić i uruchomić zewnętrzny skrypt w tle? . Pozdrawiam;)
olibre

1
Cześć Artem. Proszę przyjąć odpowiedź Dana, ponieważ (1) więcej głosów, (2) subprocess.Popen()to nowy zalecany sposób od 2010 r. (Jesteśmy teraz w 2015 r.) I (3) zduplikowane pytanie przekierowujące tutaj również ma akceptowaną odpowiedź na temat subprocess.Popen(). Pozdrawiam :-)
olibre

1
@olibre W rzeczywistości odpowiedź powinna brzmieć subprocess.Popen("<command>")w pliku <command> prowadzonym przez odpowiedni shebang. Działa idealnie dla mnie (Debian) ze skryptami bash i python, domyślnie shellsi przetrwa proces nadrzędny. stdoutidzie do tego samego terminalu, co rodzic. To działa podobnie jak &w powłoce, która była żądaniem OP. Ale do diabła, wszystkie pytania działają bardzo skomplikowane, a krótkie testy pokazały to w
mgnieniu oka

W tle może zobacz także stackoverflow.com/a/51950538/874188
tripleee

Odpowiedzi:


84

Uwaga : ta odpowiedź jest mniej aktualna niż w momencie opublikowania w 2009 roku. Korzystanie z subprocessmodułu przedstawionego w innych odpowiedziach jest teraz zalecane w dokumentacji

(Należy pamiętać, że moduł podprocesu zapewnia bardziej zaawansowane funkcje do tworzenia nowych procesów i wyszukiwania ich wyników; korzystanie z tego modułu jest lepsze niż korzystanie z tych funkcji).


Jeśli chcesz, aby proces rozpoczął się w tle, możesz użyć go system()i wywołać w taki sam sposób, jak skrypt powłoki lub możesz spawn:

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

(lub alternatywnie możesz wypróbować mniej przenośną os.P_NOWAITflagę).

Zobacz dokumentację tutaj .


8
Uwaga: musisz podać pełną ścieżkę do pliku wykonywalnego. Ta funkcja nie będzie używać zmiennej PATH, a wariant, który z niej korzysta, nie jest dostępny w systemie Windows.
sorin

36
prosto w górę powoduje awarię Pythona.
pablo

29
os.P_DETACH został zastąpiony przez os.P_NOWAIT.
się

7
Czy osoby sugerujące użycie mogą subprocessdać nam wskazówkę, jak odłączyć proces subprocess?
rakslice

3
Jak mogę użyć skryptu Python (powiedzmy attach.py), aby znaleźć proces w tle i przekierować swoje IO, aby attach.py ​​mógł czytać / zapisywać w tle jakiś długotrwały_prog?
raof01

376

Podczas gdy rozwiązanie jkp działa, nowszym sposobem robienia rzeczy (i zaleceniami dokumentacji) jest użycie subprocessmodułu. W przypadku prostych poleceń jest to odpowiednik, ale oferuje więcej opcji, jeśli chcesz zrobić coś skomplikowanego.

Przykład twojej sprawy:

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

To będzie działać rm -r some.filew tle. Zauważ, że wywołanie .communicate()zwróconego obiektu Popenbędzie blokować, dopóki się nie zakończy, więc nie rób tego, jeśli chcesz, aby działał w tle:

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

Zobacz dokumentację tutaj .

Należy również wyjaśnić: „Tło”, kiedy go używasz, jest wyłącznie koncepcją powłoki; technicznie masz na myśli to, że chcesz odrodzić proces bez blokowania go podczas oczekiwania na jego zakończenie. Jednak użyłem tutaj „tła”, aby odnieść się do zachowania przypominającego tło powłoki.


7
@Dan: Jak zabić proces, gdy działa w tle? Chcę go uruchomić przez chwilę (jest to demon, z którym mam interakcję) i zabić go, gdy skończę. Dokumenty nie są pomocne ...
Juan

1
@Dan, ale czy nie muszę za to znać PID? Monitor aktywności / Menedżer zadań nie jest opcją (musi nastąpić programowo).
Juan

5
ok, więc jak zmusić proces do tła, gdy potrzebujesz wyniku Popen () do zapisu na jego standardowe wejście?
Michael,

2
@JFSebastian: Zinterpretowałem to jako „jak mogę stworzyć niezależny proces, który nie zatrzyma wykonywania bieżącego programu”. Jak zasugerowałbyś, żebym go edytował, aby to bardziej wyrazić?
Dan

1
@ Dan: poprawna odpowiedź to: użyj, Popen()aby uniknąć blokowania głównego wątku, a jeśli potrzebujesz demona, spójrz na python-daemonpakiet, aby zrozumieć, jak powinien zachowywać się dobrze zdefiniowany demon. Twoja odpowiedź jest poprawna, jeśli usuniesz wszystko zaczynające się od „Ale bądź ostrożny”, z wyjątkiem linku do dokumentów podprocesu.
jfs

47

Prawdopodobnie potrzebujesz odpowiedzi na „Jak wywołać zewnętrzne polecenie w Pythonie” .

Najprostszym podejściem jest użycie os.systemfunkcji, np .:

import os
os.system("some_command &")

Zasadniczo wszystko, co przekażesz do systemfunkcji, zostanie wykonane tak samo, jakbyś przekazał ją do powłoki w skrypcie.


9
IMHO, skrypty Pythona są zwykle pisane jako wieloplatformowe, a jeśli istnieje proste rozwiązanie wieloplatformowe, lepiej się z tym trzymać. Nigdy nie wiem, czy w przyszłości będziesz musiał współpracować z inną platformą :) Albo czy jakiś inny człowiek chciałby migrować twój skrypt na swoją platformę.
d9k

7
To polecenie jest synchroniczne (tzn. Zawsze czeka na zakończenie uruchomionego procesu).
tav

@ d9k czy system OS.system nie jest przenośny?
lucid_dreamer

1
@ d9k nie jest wybór, aby uruchomić coś w tle, co już pozycjonuje cię w posix-land? Co byś zrobił na Windowsie? Uruchomić jako usługę?
lucid_dreamer

jak mogę tego użyć, jeśli muszę uruchomić polecenie z określonego folderu?
mrRobot

29

Znalazłem to tutaj :

W systemie Windows (Win XP) proces nadrzędny nie zakończy się, dopóki longtask.pynie zakończy pracy. To nie jest to, czego chcesz w skrypcie CGI. Problem nie jest specyficzny dla Pythona, w społeczności PHP problemy są takie same.

Rozwiązaniem jest przekazanie DETACHED_PROCESS flagi tworzenia procesu do podstawowej CreateProcessfunkcji w win API. Jeśli akurat masz zainstalowany pywin32, możesz zaimportować flagę z modułu win32process, w przeciwnym razie powinieneś ją zdefiniować samodzielnie:

DETACHED_PROCESS = 0x00000008

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

6
+1 za pokazanie, jak zachować identyfikator procesu. A jeśli ktoś chce później zabić program z identyfikatorem procesu: stackoverflow.com/questions/17856928/...
iChux

1
Wydaje się, że to tylko Windows
Audrius Meskauskas

24

Używaj subprocess.Popen()z close_fds=Trueparametrem, który pozwoli odłączyć spawnowany podproces od samego procesu Pythona i kontynuować działanie nawet po wyjściu z Pythona.

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
W systemie Windows nie odłącza się, ale działa przy użyciu parametru
createflags

3
To rozwiązanie pozostawia podproces jako Zombie w systemie Linux.
TitanFighter,

@TitanFighter można tego uniknąć, ustawiając SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009

dzięki @ Jimmy twoja odpowiedź jest JEDYNYM rozwiązaniem, które działa dla mnie.
sailfish009

12

Prawdopodobnie chcesz rozpocząć badanie modułu os w celu tworzenia różnych wątków (otwierając sesję interaktywną i udzielając pomocy (os)). Odpowiednimi funkcjami są fork i dowolne z nich. Aby dać ci pomysł, jak zacząć, umieść coś takiego w funkcji wykonującej rozwidlenie (funkcja musi wziąć listę lub krotkę „argumenty” jako argument zawierający nazwę programu i jego parametry; możesz również chcieć aby zdefiniować standardowe wejście, wyjście i błąd dla nowego wątku):

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()jest naprawdę przydatny, ale ma znaczącą wadę, ponieważ jest dostępny tylko na * nix.
Evan Fosmark

Jedyny problem z os.fork polega na tym, że jest on specyficzny dla Win32.
jkp

Więcej szczegółów na temat tego podejścia: Tworzenie demona w języku Python
Amir Ali Akbari

Możesz także osiągnąć podobne efekty za pomocą threading: stackoverflow.com/a/53751896/895245 Myślę, że to może działać w systemie Windows.
Ciro Santilli 12 冠状 病 六四 事件 法轮功

9

Oba wychwytują dane wyjściowe i działają w tle za pomocą threading

Jak wspomniano w tej odpowiedzi , jeśli przechwycisz wynik za pomocą, stdout=a następnie spróbujesz read(), proces zostanie zablokowany.

Są jednak przypadki, w których jest to potrzebne. Na przykład chciałem uruchomić dwa procesy, które rozmawiają przez port między nimi , i zapisać ich stdout w pliku dziennika i stdout.

threadingModuł pozwala nam to zrobić.

Najpierw spójrz na to, jak wykonać część przekierowania wyjścia samodzielnie w tym pytaniu: Python Popen: Zapisuj jednocześnie do standardowego pliku dziennika AND

Następnie:

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)

Po bieganiu:

./main.py

stdout jest aktualizowany co 0,5 sekundy dla każdych dwóch wierszy, które zawierają:

0
10
1
11
2
12
3
13

a każdy plik dziennika zawiera odpowiedni dziennik dla danego procesu.

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

Testowane na Ubuntu 18.04, Python 3.6.7.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.