Як запустити фоновий процес у Python?


295

Я намагаюся перенести скрипт оболонки на значно більш читану версію python. Оригінальний скрипт оболонки запускає кілька процесів (утиліти, монітори тощо) у фоновому режимі з "&". Як я можу досягти такого ж ефекту в python? Я хотів би, щоб ці процеси не загинули, коли сценарії python завершені. Я впевнений, що це якось пов’язано з концепцією демона, але я не міг знайти, як це зробити легко.



1
Привіт Артеме. Будь ласка, прийміть відповідь Дана, оскільки (1) більше голосів, (2) subprocess.Popen()- це новий рекомендований шлях з 2010 року (ми зараз у 2015 році), і (3) повторне питання, що перенаправляється тут, також має прийняту відповідь subprocess.Popen(). Ура :-)
олібре

1
@olibre Насправді відповідь має бути subprocess.Popen("<command>")у файлі <command> на чолі з відповідним шебангом. Працює для мене ідеально (Debian) з bash та python-скриптами, по суті shell, і переживає свій батьківський процес. stdoutйде до того ж терміналу, ніж батьківський. Таким чином, це працює як &у оболонці, яка була запитом ОП. Але, пекло, усі питання
складаються

Для фону можна побачити також 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
прямо в мене випадає пітон.
пабло

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
@Dan, але чи не потрібно мені знати PID для цього? Моніторинг активності / Диспетчер завдань не є варіантом (має відбуватися програмно).
Хуан

5
Ок, так як змусити процес перетворити фоновий фон, коли потрібен результат, щоб Popen () писав на його stdin?
Майкл

2
@JFSebastian: Я трактував це як "як я можу створити незалежний процес, який не зупиняє виконання поточної програми". Як би ви запропонували мені це відредагувати, щоб зробити це більш явним?
Дан

1
@Dan: правильна відповідь: використовуйте, Popen()щоб уникнути блокування основного потоку, а якщо вам потрібен демон, то подивіться на python-daemonпакет, щоб зрозуміти, як повинен вести себе добре визначений демон. Ваша відповідь гарна, якщо ви видалите все, починаючи з "Але будьте обережні", за винятком посилання на документи підпроцесу ".
jfs

47

Ви, мабуть, хочете відповіді на тему "Як викликати зовнішню команду в Python" .

Найпростіший підхід - це використання os.systemфункції, наприклад:

import os
os.system("some_command &")

В основному, все, що ви systemпередаєте функції, буде виконано так само, як якщо б ви передали його в оболонку в сценарії.


10
IMHO, сценарії python, як правило, пишуться як кросплатформні, і якщо існує просте кросплатформенне рішення, краще дотримуватися цього. Ніколи не знаю, чи доведеться вам працювати з іншою платформою в майбутньому :) Або якщо хтось інший захоче перенести ваш сценарій на свою платформу.
d9k

7
Ця команда є синхронною (тобто вона завжди чекає припинення запущеного процесу).
тав

@ d9k є os.system не портативний?
lucid_dreamer

1
@ d9k - це не вибір того, щоб запустити щось у фоновому режимі, вже розміщуючи вас у posix-land? Що б ви робили в Windows? Запустити як послугу?
lucid_dreamer

як я можу це використовувати, якщо мені потрібно запустити команду з певної папки?
mrRobot

29

Я знайшов це тут :

У Windows (win xp) батьківський процес не закінчиться до longtask.py не закінчиться робота. Це не те, що потрібно в CGI-скрипті. Проблема не характерна для Python, у PHP-спільноті проблеми однакові.

Рішення полягає в тому, щоб передати DETACHED_PROCESS прапор створення процесу базовій CreateProcessфункції в програмі Win. Якщо у вас встановлено pywin32, ви можете імпортувати прапор з модуля win32process, інакше слід визначити його самостійно:

DETACHED_PROCESS = 0x00000008

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

6
+1 - показує, як зберегти ідентифікатор процесу І якщо хто -то хоче вбити програму пізніше з ідентифікатором процесу: stackoverflow.com/questions/17856928 / ...
iChux

1
Це здається лише для Windows
Audrius Meskauskas

24

Використовуйте subprocess.Popen()з close_fds=Trueпараметром, який дозволить відокремленому підпроцесу відірватися від самого процесу 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

спасибі @Jimmy, твоя відповідь - ТІЛЬКО рішення працює для мене.
sailfish009

12

Напевно, ви хочете почати досліджувати модуль os для розгортання різних потоків (відкривши інтерактивну сесію та видавши довідку (os)). Відповідні функції - це вилка та будь-яка з виконуваних. Щоб дати вам уявлення про те, як запустити, помістіть щось подібне у функцію, яка виконує вилку (функція повинна взяти список або перекласти "аргументи" як аргумент, що містить ім'я програми та її параметри; ви також можете захотіти визначити 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()це дійсно корисно, але у нього є помітний мінус, коли він доступний лише на * nix.
Еван Фосмарк

Єдина проблема з os.fork полягає в тому, що він специфічний для win32.
jkp

Детальніше про цей підхід: Створення демона шляхом Python
Амір Алі Акбарі

Ви також можете досягти подібних ефектів за допомогою threading: stackoverflow.com/a/53751896/895245 Я думаю, що це може працювати в Windows.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

9

Обидва захоплюють вихід і працюють на тлі threading

Як вже було сказано в цій відповіді , якщо ви захоплюєте вихід stdout=і намагаєтеся read(), процес блокується.

Однак є випадки, коли це вам потрібно. Наприклад, я хотів запустити два процеси, які розмовляють через порт між ними , і зберегти їх stdout у лог-файл та stdout.

threadingМодуль дозволяє нам зробити це.

По-перше, подивіться, як виконати частину перенаправлення виводу самостійно в цьому запитанні: Python Popen: Запишіть файл stdout AND log одночасно

Тоді:

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.