จะเริ่มกระบวนการพื้นหลังใน Python ได้อย่างไร


295

ฉันพยายามที่จะพอร์ตเชลล์สคริปต์ไปยังรุ่นหลามอ่านได้มากขึ้น เชลล์สคริปต์ดั้งเดิมเริ่มต้นกระบวนการหลายอย่าง (ยูทิลิตีมอนิเตอร์ ฯลฯ ) ในเบื้องหลังด้วย "&" ฉันจะได้รับเอฟเฟกต์เดียวกันในงูหลามได้อย่างไร? ฉันต้องการให้กระบวนการเหล่านี้ไม่ตายเมื่อสคริปต์ไพ ธ อนเสร็จสมบูรณ์ ฉันแน่ใจว่ามันเกี่ยวข้องกับแนวคิดของภูต แต่อย่างใด แต่ฉันไม่สามารถหาวิธีการได้อย่างง่ายดาย



1
สวัสดีอาร์เตม โปรดยอมรับคำตอบของ Danเพราะ (1) คะแนนมากขึ้น (2) subprocess.Popen()เป็นวิธีที่แนะนำใหม่ตั้งแต่ปี 2010 (เราอยู่ในปี 2015 ในขณะนี้) และ (3) คำถามที่ทำซ้ำการเปลี่ยนเส้นทางที่นี่มีคำตอบที่ยอมรับsubprocess.Popen()เช่นกัน ไชโย :-)
olibre

1
@olibre อันที่จริงคำตอบควรอยู่subprocess.Popen("<command>")กับไฟล์ <command> นำโดย shebang ที่เหมาะสม ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน (Debian) ด้วยสคริปต์ทุบตีและงูหลามโดยปริยายshellและอยู่รอดในกระบวนการหลัก stdoutไปที่เทอร์มินัลเดียวกันกับของผู้ปกครอง ดังนั้นสิ่งนี้จึงทำงานคล้าย&กับเชลล์ซึ่งเป็นคำขอ OPs แต่นรกคำถามทุกข้อนั้นซับซ้อนมากในขณะที่การทดสอบเล็กน้อยแสดงให้เห็นในเวลาไม่นาน)
flaschbier

สำหรับพื้นหลังอาจดูได้ที่stackoverflow.com/a/51950538/874188
tripleee

คำตอบ:


84

หมายเหตุ : คำตอบนี้เป็นปัจจุบันน้อยกว่าเมื่อโพสต์ในปี 2009 แนะนำให้ใช้subprocessโมดูลที่แสดงในคำตอบอื่น ๆในเอกสาร

(โปรดทราบว่าโมดูลย่อยให้บริการที่มีประสิทธิภาพมากขึ้นสำหรับการวางไข่กระบวนการใหม่และดึงผลลัพธ์ของพวกเขาโดยใช้โมดูลนั้นจะดีกว่าการใช้ฟังก์ชั่นเหล่านี้)


หากคุณต้องการให้กระบวนการของคุณเริ่มต้นในพื้นหลังคุณสามารถใช้system()และเรียกมันในแบบเดียวกับที่เชลล์สคริปต์ใช้หรือคุณสามารถทำได้spawn:

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

(หรือมิฉะนั้นคุณอาจลองพกพาน้อยลง os.P_NOWAITธง )

ดูเอกสารที่นี่


8
หมายเหตุ: คุณต้องระบุพา ธ เต็มไปยังไฟล์ที่เรียกทำงานได้ ฟังก์ชั่นนี้จะไม่ใช้ตัวแปร PATH และตัวแปรที่ใช้จะไม่สามารถใช้งานได้กับ Windows
sorin

36
หลามตรงขึ้นล่มสำหรับฉัน
pablo

29
os.P_DETACH ถูกแทนที่ด้วย os.P_NOWAIT
ตัวเอง

7
สามารถบอกคนใช้subprocessให้เราคำแนะนำวิธีการถอดกระบวนการด้วยsubprocess?
rakslice

3
ฉันจะใช้สคริปต์ Python (เช่น attach.py) เพื่อค้นหากระบวนการพื้นหลังได้อย่างไรและเปลี่ยนเส้นทางของ IO เพื่อให้ attach.py ​​สามารถอ่าน / เขียนจาก some_long_running_prog ในพื้นหลังได้
raof01

376

ในขณะที่วิธีการแก้ปัญหาของjkpทำงานวิธีการใหม่ในการทำสิ่งต่าง ๆ (และวิธีการที่เอกสารแนะนำ) คือการใช้subprocessโมดูล สำหรับคำสั่งง่าย ๆ มันเทียบเท่า แต่มีตัวเลือกเพิ่มเติมถ้าคุณต้องการทำอะไรที่ซับซ้อน

ตัวอย่างสำหรับกรณีของคุณ:

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

สิ่งนี้จะทำงานrm -r some.fileในพื้นหลัง โปรดทราบว่าการเรียก.communicate()วัตถุที่ส่งคืนมาจากPopenจะบล็อกจนกว่าจะเสร็จสมบูรณ์ดังนั้นอย่าทำเช่นนั้นหากคุณต้องการให้มันทำงานในพื้นหลัง:

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

ดูเอกสารประกอบที่นี่ที่นี่

นอกจากนี้ประเด็นของการชี้แจง: "พื้นหลัง" ในขณะที่คุณใช้ที่นี่เป็นแนวคิดของเชลล์อย่างหมดจด ในทางเทคนิคสิ่งที่คุณหมายถึงคือคุณต้องการวางไข่กระบวนการโดยไม่ปิดกั้นในขณะที่คุณรอให้กระบวนการเสร็จสมบูรณ์ อย่างไรก็ตามฉันใช้ "พื้นหลัง" ที่นี่เพื่ออ้างถึงพฤติกรรมที่คล้ายกับเปลือกพื้นหลัง


7
@Dan: ฉันจะฆ่ากระบวนการเมื่อทำงานในพื้นหลังได้อย่างไร ฉันต้องการเรียกใช้มันสักพัก (มันเป็นภูตที่ฉันมีปฏิสัมพันธ์กับ) และฆ่ามันเมื่อฉันทำมันเสร็จแล้ว เอกสารจะไม่เป็นประโยชน์ ...

1
@ ต้องทำ แต่ฉันไม่จำเป็นต้องรู้ PID การตรวจสอบกิจกรรม / ตัวจัดการงานไม่ใช่ตัวเลือก (ต้องเกิดขึ้นโดยทางโปรแกรม)
Juan

5
ตกลงดังนั้นคุณจะบังคับให้กระบวนการทำงานอย่างไรเมื่อคุณต้องการผลลัพธ์ของ Popen () เพื่อเขียนไปยัง stdin
Michael

2
@JFSebastian: ฉันตีความว่าเป็น "ฉันจะสร้างกระบวนการอิสระที่ไม่หยุดการทำงานของโปรแกรมปัจจุบัน" ได้อย่างไร คุณจะแนะนำให้ฉันแก้ไขมันอย่างไรเพื่อให้ชัดเจนยิ่งขึ้น?
Dan

1
@Dan: คำตอบที่ถูกต้องคือ: ใช้Popen()เพื่อหลีกเลี่ยงการบล็อกเธรดหลักและหากคุณต้องการ daemon ให้ดูpython-daemonแพ็คเกจเพื่อทำความเข้าใจว่า daemon ที่กำหนดไว้ดีควรทำงานอย่างไร คำตอบของคุณก็โอเคถ้าคุณลบทุกอย่างที่ขึ้นต้นด้วย "แต่ระวัง" ยกเว้นลิงก์ไปยังเอกสารของกระบวนการย่อย
jfs

47

คุณอาจต้องการคำตอบ"วิธีเรียกคำสั่งภายนอกใน Python" "วิธีการเรียกคำสั่งภายนอกงูใหญ่"

วิธีที่ง่ายที่สุดคือการใช้os.systemฟังก์ชั่นเช่น:

import os
os.system("some_command &")

โดยพื้นฐานแล้วสิ่งที่คุณส่งไปยังsystemฟังก์ชั่นจะถูกดำเนินการเช่นเดียวกับถ้าคุณส่งไปยังเชลล์ในสคริปต์


10
IMHO สคริปต์ python มักจะเขียนเป็น cross-platform และหากมีวิธีการแก้ปัญหาข้ามแพลตฟอร์มที่ง่าย ๆ ไม่เคยรู้ว่าคุณจะต้องทำงานกับแพลตฟอร์มอื่นในอนาคต :) หรือถ้ามีคนอื่นต้องการย้ายสคริปต์ของคุณไปยังแพลตฟอร์มของเขา
d9k

7
คำสั่งนี้เป็นแบบซิงโครนัส (นั่นคือมันจะรอการยกเลิกกระบวนการเริ่มต้นเสมอ)
tav

@ d9k ไม่ได้เป็นระบบปฏิบัติการแบบพกพา s.system?
lucid_dreamer

1
@ d9k ไม่ใช่ทางเลือกของการทำงานบางอย่างในพื้นหลังที่วางตำแหน่งคุณใน posix-land หรือไม่? คุณจะทำอะไรใน Windows เรียกใช้เป็นบริการหรือไม่
lucid_dreamer

ฉันจะใช้สิ่งนี้ได้อย่างไรหากฉันต้องการเรียกใช้คำสั่งจากโฟลเดอร์เฉพาะ
mrRobot

29

ฉันพบสิ่งนี้ที่นี่ :

บน windows (win xp) กระบวนการหลักจะไม่เสร็จสิ้นจนกว่าการทำงานlongtask.pyจะเสร็จสิ้น ไม่ใช่สิ่งที่คุณต้องการใน CGI-script ปัญหาไม่ได้ระบุเฉพาะกับ Python ในชุมชน PHP ปัญหานั้นเหมือนกัน

การแก้ปัญหาคือการส่งผ่านDETACHED_PROCESS กระบวนการสร้างธงไปยังCreateProcessฟังก์ชั่นพื้นฐานใน win API หากคุณติดตั้ง pywin32 คุณสามารถนำเข้าการตั้งค่าสถานะจากโมดูล win32process มิฉะนั้นคุณควรกำหนดด้วยตัวคุณเอง:

DETACHED_PROCESS = 0x00000008

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

6
+1 สำหรับการแสดงวิธีการรักษารหัสกระบวนการ และถ้าใครต้องการฆ่าโปรแกรมในภายหลังด้วย id กระบวนการ: stackoverflow.com/questions/17856928/ …
iChux

1
ดูเหมือนว่า Windows เท่านั้น
Audrius Meskauskas

24

ใช้subprocess.Popen()กับclose_fds=Trueพารามิเตอร์ซึ่งจะช่วยให้การแยก subprocess เกิดจากกระบวนการ Python เองและทำงานต่อไปแม้หลังจากออกจาก 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
ใน windows มันไม่แยกออก แต่ใช้งานพารามิเตอร์
createflags

3
วิธีการแก้ปัญหานี้ทำให้กระบวนการย่อยเป็น Zombie บน Linux
TitanFighter

@ TitanFighter นี้สามารถหลีกเลี่ยงได้โดยการตั้งค่า SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009

ขอบคุณ @ จิมมี่คำตอบของคุณคือทางออกเดียวสำหรับฉัน
sailfish009

12

คุณอาจต้องการเริ่มตรวจสอบโมดูลระบบปฏิบัติการเพื่อฟอร์กเธรดที่แตกต่างกัน (โดยการเปิดเซสชันแบบโต้ตอบและการออกความช่วยเหลือ (os)) ฟังก์ชั่นที่เกี่ยวข้องคือ fork และสิ่งใด ๆ ของ exec เพื่อให้แนวคิดเกี่ยวกับวิธีการเริ่มต้นให้วางบางสิ่งเช่นนี้ไว้ในฟังก์ชันที่ดำเนินการแยก (ฟังก์ชันต้องใช้รายการหรือ tuple 'args' เป็นอาร์กิวเมนต์ที่มีชื่อโปรแกรมและพารามิเตอร์คุณอาจต้องการ เพื่อกำหนด stdin, out และ err สำหรับเธรดใหม่):

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()มีประโยชน์จริง ๆ แต่มีข้อเสียที่น่าทึ่งของการมีอยู่ใน * ระวังเท่านั้น
Evan Fosmark

ปัญหาเดียวของ os.fork คือ win32 เฉพาะ
jkp

รายละเอียดเพิ่มเติมเกี่ยวกับวิธีการนี้: การสร้างภูตด้วยวิธี Python
Amir Ali Akbari

คุณสามารถเข้าถึงเอฟเฟกต์ที่คล้ายกันได้ด้วยthreading: stackoverflow.com/a/53751896/895245ฉันคิดว่ามันอาจใช้งานได้บน Windows
Ciro Santilli 郝海东冠状病六四事件法轮功

9

ทั้งจับเอาท์พุทและทำงานบนพื้นหลังด้วย threading

ตามที่กล่าวไว้ ในคำตอบนี้ถ้าคุณจับเอาท์พุทด้วยstdout=แล้วลองread()บล็อกกระบวนการ

อย่างไรก็ตามมีหลายกรณีที่คุณต้องการสิ่งนี้ ตัวอย่างเช่นฉันต้องการเปิดตัวสองกระบวนการที่พูดถึงพอร์ตระหว่างพวกเขาและบันทึก stdout ของพวกเขาไปยังล็อกไฟล์และ stdout

threadingโมดูลช่วยให้เราสามารถทำเช่นนั้นได้

ก่อนอื่นให้ดูที่การทำส่วนการเปลี่ยนเส้นทางเอาต์พุตเพียงอย่างเดียวในคำถามนี้: Python Popen: เขียนไปยัง stdout และล็อกไฟล์พร้อมกัน

แล้ว:

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)

หลังจากทำงาน:

./main.py

stdout รับการอัพเดททุก 0.5 วินาทีเพื่อให้ทุกสองบรรทัดมี:

0
10
1
11
2
12
3
13

และไฟล์บันทึกแต่ละไฟล์จะมีบันทึกที่เกี่ยวข้องสำหรับกระบวนการที่กำหนด

แรงบันดาลใจจาก: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/

ทดสอบกับ Ubuntu 18.04, Python 3.6.7

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.