Bagaimana cara memulai proses latar belakang dengan Python?

295

Saya mencoba untuk mem-porting skrip shell ke versi python yang lebih mudah dibaca. Skrip shell asli memulai beberapa proses (utilitas, monitor, dll.) Di latar belakang dengan "&". Bagaimana saya bisa mencapai efek yang sama dengan python? Saya ingin proses ini tidak mati ketika skrip python selesai. Saya yakin ini terkait dengan konsep daemon, tapi saya tidak bisa menemukan cara melakukan ini dengan mudah.

Artem
sumber
1
Pertanyaan yang benar-benar terduplikasi adalah Bagaimana menjalankan dan menjalankan skrip eksternal di latar belakang? . Cheers;)
olibre
1
Hai Artem. Harap terima jawaban Dan karena (1) lebih banyak suara, (2) subprocess.Popen()adalah cara yang disarankan baru sejak 2010 (kami berada di 2015 sekarang) dan (3) pertanyaan duplikat yang mengarahkan di sini juga memiliki jawaban yang diterima tentang subprocess.Popen(). Cheers :-)
olibre
1
@ Colibre Sebenarnya jawabannya harus subprocess.Popen("<command>")dengan file <command> yang dipimpin oleh shebang yang cocok. Bekerja sempurna untuk saya (Debian) dengan skrip bash dan python, secara implisit shells dan bertahan dari proses induknya. stdoutpergi ke terminal yang sama dari orang tua. Jadi ini berfungsi seperti &dalam shell yang merupakan permintaan OPs. Tapi sih, semua pertanyaan berjalan sangat kompleks sementara pengujian kecil menunjukkannya dalam waktu singkat;)
flaschbier
Untuk latar belakang mungkin lihat juga stackoverflow.com/a/51950538/874188
tripleee

Jawaban:

84

Catatan : Jawaban ini kurang terkini daripada saat diposting pada tahun 2009. Menggunakan subprocessmodul yang ditunjukkan dalam jawaban lain sekarang direkomendasikan dalam dokumen

(Perhatikan bahwa modul subproses menyediakan fasilitas yang lebih kuat untuk menghasilkan proses baru dan mengambil hasilnya; menggunakan modul itu lebih baik daripada menggunakan fungsi-fungsi ini.)


Jika Anda ingin proses Anda dimulai di latar belakang, Anda dapat menggunakan system()dan memanggilnya dengan cara yang sama dengan skrip shell Anda, atau Anda dapat spawnmelakukannya:

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

(Atau, sebagai alternatif, Anda dapat mencoba os.P_NOWAITbendera yang kurang portabel ).

Lihat dokumentasi di sini .

jkp
sumber
8
Catatan: Anda harus menentukan jalur lengkap ke yang dapat dieksekusi. Fungsi ini tidak akan menggunakan variabel PATH dan varian yang menggunakannya tidak tersedia di Windows.
sorin
36
lurus ke atas crash python untuk saya.
pablo
29
os.P_DETACH telah diganti dengan os.P_NOWAIT.
diri sendiri
7
Bisakah orang-orang yang menyarankan menggunakan subprocessmemberi kita petunjuk bagaimana melepaskan suatu proses subprocess?
rakslice
3
Bagaimana saya bisa menggunakan skrip Python (misalnya attach.py) untuk menemukan proses latar belakang dan mengarahkan ulang IO-nya sehingga attach.py ​​dapat membaca dari / menulis ke some_long_running_prog di latar belakang?
raof01
376

Sementara solusi jkp bekerja, cara yang lebih baru dalam melakukan sesuatu (dan cara dokumentasi merekomendasikan) adalah dengan menggunakan subprocessmodul. Untuk perintah sederhana, ini setara, tetapi menawarkan lebih banyak opsi jika Anda ingin melakukan sesuatu yang rumit.

Contoh untuk kasus Anda:

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

Ini akan berjalan rm -r some.filedi latar belakang. Perhatikan bahwa memanggil .communicate()objek yang dikembalikan dari Popenakan memblokir hingga selesai, jadi jangan lakukan itu jika Anda ingin menjalankannya di latar belakang:

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

Lihat dokumentasi di sini .

Juga, poin klarifikasi: "Latar Belakang" seperti yang Anda gunakan di sini adalah murni konsep shell; secara teknis, yang Anda maksud adalah Anda ingin menelurkan proses tanpa memblokir sementara Anda menunggu untuk menyelesaikannya. Namun, saya telah menggunakan "latar belakang" di sini untuk merujuk pada perilaku seperti latar belakang shell.

Dan
sumber
7
@ Dan: Bagaimana cara saya mematikan proses setelah itu berjalan di latar belakang? Saya ingin menjalankannya untuk sementara waktu (ini adalah dasmon yang saya berinteraksi dengan) dan membunuhnya ketika saya selesai dengan itu. Dokumen tidak membantu ...
Juan
1
@Bisakah saya tidak tahu PID untuk itu? Monitor aktivitas / Task manager bukan opsi (perlu terjadi secara terprogram).
Juan
5
ok jadi bagaimana Anda memaksa proses ke latar belakang ketika Anda membutuhkan hasil dari Popen () untuk menulis ke stdin?
Michael
2
@ JFSebastian: Saya menafsirkannya sebagai "bagaimana saya bisa membuat proses independen yang tidak menghentikan pelaksanaan program saat ini". Bagaimana Anda menyarankan saya mengeditnya untuk membuat ini lebih eksplisit?
Dan
1
@Dan: jawaban yang benar adalah: gunakan Popen()untuk menghindari pemblokiran utas utama dan jika Anda memerlukan daemon kemudian lihat python-daemonpaket untuk memahami bagaimana seharusnya daemon yang didefinisikan dengan baik. Jawaban Anda baik-baik saja jika Anda menghapus semua yang dimulai dengan "Tapi hati-hati" kecuali tautan ke dokumen subproses.
jfs
47

Anda mungkin menginginkan jawaban untuk "Cara memanggil perintah eksternal dengan Python" .

Pendekatan paling sederhana adalah dengan menggunakan os.systemfungsi, misalnya:

import os
os.system("some_command &")

Pada dasarnya, apa pun yang Anda berikan ke systemfungsi akan dieksekusi sama seperti jika Anda meneruskannya ke shell dalam sebuah skrip.

Eli Courtwright
sumber
9
IMHO, skrip python biasanya ditulis sebagai cross-platform dan jika ada solusi cross-platform sederhana ada lebih baik untuk tetap menggunakannya. Tidak pernah tahu apakah Anda harus bekerja dengan platform lain di masa depan :) Atau jika ada orang lain yang ingin memigrasikan skrip Anda ke platformnya.
d9k
7
Perintah ini sinkron (artinya selalu menunggu penghentian proses yang dimulai).
tav
@ d9k apakah os.system tidak portabel?
lucid_dreamer
1
@ d9k bukankah pilihan menjalankan sesuatu di latar belakang sudah memposisikan Anda di posix-land? Apa yang akan kamu lakukan pada Windows? Jalankan sebagai layanan?
lucid_dreamer
bagaimana saya bisa menggunakan ini jika saya perlu menjalankan perintah dari folder tertentu?
mrRobot
29

Saya menemukan ini di sini :

Di windows (win xp), proses induk tidak akan selesai sampai pekerjaannya longtask.pyselesai. Ini bukan yang Anda inginkan dalam skrip CGI. Masalahnya tidak spesifik untuk Python, dalam komunitas PHP masalahnya sama.

Solusinya adalah dengan melewati DETACHED_PROCESS Bendera Proses Pembuatan ke CreateProcessfungsi yang mendasari di win API. Jika Anda menginstal pywin32, Anda dapat mengimpor flag dari modul win32process, jika tidak, Anda harus mendefinisikannya sendiri:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid
fp
sumber
6
+1 untuk menunjukkan cara mempertahankan id proses. Dan jika ada yang ingin mematikan program nanti dengan proses id: stackoverflow.com/questions/17856928/…
iChux
1
Ini sepertinya hanya Windows
Audrius Meskauskas
24

Gunakan subprocess.Popen()dengan close_fds=Trueparameter, yang akan memungkinkan subproses yang dihasilkan terlepas dari proses Python itu sendiri dan terus berjalan bahkan setelah Python keluar.

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'
Jimmy Yin
sumber
1
Di windows, itu tidak terlepas tetapi menggunakan karya penciptaanflags parameter
SmartManoj
3
Solusi ini meninggalkan subproses sebagai Zombie di Linux.
TitanFighter
@TitanFighter ini dapat dihindari dengan mengatur SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009
terima kasih @ Jimmy jawaban Anda adalah solusi HANYA berfungsi untuk saya.
sailfish009
12

Anda mungkin ingin mulai menyelidiki modul os untuk mencari utas yang berbeda (dengan membuka sesi interaktif dan mengeluarkan bantuan (os)). Fungsi-fungsi yang relevan adalah fork dan fungsi-fungsi eksekutif mana saja. Untuk memberi Anda ide tentang cara memulainya, letakkan sesuatu seperti ini di dalam fungsi yang melakukan fork (fungsi perlu mengambil daftar atau tuple 'args' sebagai argumen yang berisi nama program dan parameternya; Anda mungkin juga ingin untuk mendefinisikan stdin, out, dan err untuk utas baru):

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)
Gerald Senarclens de Grancy
sumber
2
os.fork()benar-benar berguna, tetapi memang memiliki kelemahan hanya tersedia di * nix.
Evan Fosmark
Satu-satunya masalah dengan os.fork adalah spesifikasinya win32.
jkp
Lebih detail tentang pendekatan ini: Membuat daemon dengan cara Python
Amir Ali Akbari
Anda juga dapat mencapai efek serupa dengan threading: stackoverflow.com/a/53751896/895245 Saya pikir itu mungkin bekerja pada Windows.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
9

Keduanya menangkap keluaran dan berjalan di latar belakang dengan threading

Seperti disebutkan pada jawaban ini , jika Anda menangkap output dengan stdout=dan kemudian mencoba read(), maka proses tersebut akan terhambat.

Namun, ada kasus di mana Anda membutuhkan ini. Sebagai contoh, saya ingin meluncurkan dua proses yang berbicara melalui port di antara mereka , dan menyimpan stdout mereka ke file log dan stdout.

The threadingmodul memungkinkan kita untuk melakukan itu.

Pertama, lihat bagaimana melakukan bagian redirection output saja dalam pertanyaan ini: Python Popen: Menulis ke stdout DAN file log secara bersamaan

Kemudian:

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)

Setelah berlari:

./main.py

stdout diperbarui setiap 0,5 detik untuk setiap dua baris mengandung:

0
10
1
11
2
12
3
13

dan setiap file log berisi log masing-masing untuk proses yang diberikan.

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

Diuji pada Ubuntu 18.04, Python 3.6.7.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber