在后台运行时,Python脚本会挂起 [英] Python script hanging when running in the background

查看:127
本文介绍了在后台运行时,Python脚本会挂起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Python脚本(在2.7上运行),当我从命令行相对于后台运行该脚本时,其行为有所不同.当我从终端运行它时,它按预期运行,这两个线程作为守护程序运行,将输出写入窗口,而主循环则等待quit命令.它会一直运行,直到我输入quit:

I have a Python script (run on 2.7) that behaves differently when I run it in from the command line versus the background. When I run it from the terminal it runs as expected, the two threads run as daemons writing output to the window while the main loop waits for the quit command. It runs forever until I enter quit:

python test.py

当同一程序在后台运行时,两个线程都运行一次,然后程序挂起(我将其范围缩小到raw_input,我想我做出了一个错误的假设,即使两个线程将继续运行,即使在后台运行,而raw_input阻止了主线程(例如,由于在这种情况下没有输入,因此这两个线程基本上将永远运行).

When the same program is run in the background, both threads run one time and then the program hangs (I've narrowed it down to the raw_input, I guess I made a incorrect assumption that the two threads would continue on even if run in the background and raw_input blocked the main one. e.g the two threads would basically run forever since there is no input in this scenario).

python test.py &

我的目标是让一个程序运行这些循环(可能永远),但是如果我从终端运行该程序,它将接受输入.

My goal is to have one program with those loops running (potentially forever) but if I ran it from the terminal would accept input.

为了允许程序从终端/在后台运行,我是否需要在raw_input前面放置一个if语句,以检查该程序是否在后台,或者我是否缺少其他有帮助的东西? /p>

In order to allow the program to run both from the terminal / in the background do I need to basically put an if statement before the raw_input that checks whether it's in the background or not or am I missing else that would help?

import sys
import time
from threading import Thread

def threadOne():
    while True:
        print("Thread 1")
        time.sleep(1)

def threadTwo():
    while True:
        print("Thread 2")
        time.sleep(1)

# Run the threads in the background as daemons
threadOne = Thread(target = threadOne)
threadOne.daemon = True
threadOne.start()

threadTwo = Thread(target = threadTwo)
threadTwo.daemon = True
threadTwo.start()

# Main input loop.  This will allow us to enter input.  The
# threads will run forever unless "quit" is entered.  This
# doesn't run when the program is run in the background (I
# incorrectly assumed it would just run forever with no input 
# ever being entered in that scenario).
while True:
    userInput = ""
    userInput = raw_input("")
    time.sleep(1)

    # This should allow us to exit out
    if str(userInput) == "quit":
        sys.exit()

推荐答案

为了允许程序从终端/在后台运行,我是否需要在raw_input前面放置一个if语句,以检查该程序是否在后台,或者我是否缺少其他有帮助的东西? /p>

In order to allow the program to run both from the terminal / in the background do I need to basically put an if statement before the raw_input that checks whether it's in the background or not or am I missing else that would help?

以某种可能的方式 起作用(我假设您正在* nix上运行它),但是如果用户要将进程发送回后台(例如,使用 Ctrl暂停它 Z ,然后在raw_input等待用户输入时使用%&在后台将其恢复.然后,stdin上的读取将被阻止,就像在后台一样,从而导致内核停止进程,因为这是stdio的工作方式.如果可以接受(基本上用户必须在暂停过程之前按Enter键),则只需执行以下操作:

In a way this probably works (I am assuming you are running this on a *nix), however if user were to send the process backing into background (i.e. suspend it using CtrlZ then resuming it in background with %&) while raw_input is waiting on user input, then the read on stdin will then be blocked as it is in the background, thus causing the kernel to stop the process as this is how stdio works. If this is acceptable (basically user has to hit enter before suspending the process), you can simply do this:

import os

while True:
    userInput = ""
    if os.getpgrp() == os.tcgetpgrp(sys.stdout.fileno()):
        userInput = raw_input("")
    time.sleep(1)

os.getpgrp 的作用是返回当前os组的ID,然后 os.tcgetpgrp 获取与此进程的stdout关联的进程组,如果它们匹配,则表示此进程当前在前台,这意味着您可以调用raw_input而不阻塞线程.

另一个问题提出了类似的问题,我在以下地方有更长的解释:

Another question raised a similar issue and I have a longer explanation at: Freeze stdin when in the background, unfreeze it when in the foreground.

更好的方法是将其与 select.poll ,并从标准I/O单独寻址交互式I/O(直接使用/dev/tty),因为您不希望stdin/stdout重定向被污染".这是同时包含这两个想法的更完整的版本:

The better way is to combine this with select.poll, and address interactive I/O separately (by using /dev/tty directly) from standard I/O as you don't want stdin/stdout redirection being "polluted" by that. Here is the more complete version that contains both these ideas:

tty_in = open('/dev/tty', 'r')
tty_out = open('/dev/tty', 'w')
fn = tty_in.fileno()
poll = select.poll()
poll.register(fn, select.POLLIN)

while True:
    if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10):  # 10 ms
        # poll should only return if the input buffer is filled,
        # which is triggered when a user enters a complete line,
        # which lets the following readline call to not block on
        # a lack of input.
        userInput = tty_in.readline()
        # This should allow us to exit out
        if userInput.strip() == "quit":
            sys.exit()

仍需要进行背景/前景检测,因为该过程尚未完全从外壳分离(因为可以将其带回前台),因此如果发送任何输入,poll将返回tty的fileno进入外壳程序,如果这触发了readline,则会停止该过程.

The background/foreground detection is still needed as the process is not fully detached from the shell (since it can be brought back to the foreground) thus poll will return the fileno of the tty if any input is sent into the shell, and if this triggers the readline which will then stop the process.

此解决方案的优点是不需要用户按下Enter键并迅速挂起任务以在raw_input陷阱并阻止stdin停止进程之前将其发送回后台(因为poll检查是否存在输入要读取),并允许正确的stdin/stdout重定向(因为所有交互式输入都是通过/dev/tty处理的),因此用户可以执行以下操作:

This solution has the advantage of not requiring the user to hit enter and quickly suspend the task to send it back to the background before raw_input traps and blocks stdin to stop the process (as poll checks whether there's input to be read), and allow proper stdin/stdout redirection (as all interactive input is handled via /dev/tty) so users can do something like:

$ python script.py < script.py 2> stderr
input stream length: 2116

在下面的完整示例中,它还向用户提供提示,即,每当发送命令或将过程返回到前台时,都会显示>,并将整个内容包装在main函数中,并修改了第二个线程以在stderr吐出东西:

In the completed example below it also provide a prompt to the user, i.e. a > is shown whenever a command is sent or whenever the process is returned to foreground, and wrapped the entire thing in a main function, and modified the second thread to spit things out at stderr:

import os
import select
import sys
import time
from threading import Thread

def threadOne():
    while True:
        print("Thread 1")
        time.sleep(1)

def threadTwo():
    while True:
        # python 2 print does not support file argument like python 3,
        # so writing to sys.stderr directly to simulate error message.
        sys.stderr.write("Thread 2\n")
        time.sleep(1)

# Run the threads in the background
threadOne = Thread(target = threadOne)
threadOne.daemon = True

threadTwo = Thread(target = threadTwo)
threadTwo.daemon = True

def main():
    threadOne.start()
    threadTwo.start()

    tty_in = open('/dev/tty', 'r')
    tty_out = open('/dev/tty', 'w')
    fn = tty_in.fileno()
    poll = select.poll()
    poll.register(fn, select.POLLIN)

    userInput = ""
    chars = []
    prompt = True

    while True:
        if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10):  # 10 ms
            # poll should only return if the input buffer is filled,
            # which is triggered when a user enters a complete line,
            # which lets the following readline call to not block on
            # a lack of input.
            userInput = tty_in.readline()
            # This should allow us to exit out
            if userInput.strip() == "quit":
                sys.exit()
            # alternatively an empty string from Ctrl-D could be the
            # other exit method.
            else:
                tty_out.write("user input: %s\n" % userInput)
                prompt = True
        elif not os.getpgrp() == os.tcgetpgrp(fn):
            time.sleep(0.1)
            if os.getpgrp() == os.tcgetpgrp(fn):
                # back to foreground, print a prompt:
                prompt = True

        if prompt:
            tty_out.write('> ')
            tty_out.flush()
            prompt = False

if __name__ == '__main__':
    try:
        # Uncomment if you are expecting stdin
        # print('input stream length: %d ' % len(sys.stdin.read()))
        main()
    except KeyboardInterrupt:
        print("Forcibly interrupted.  Quitting")
        sys.exit()  # maybe with an error code

这是一个有趣的练习;我可以说,这是一个相当不错且有趣的问题.

Has been an interesting exercise; this was a rather good and interesting question, if I may say.

最后一点:这不是跨平台的,因为它没有select.poll/dev/tty,因此在Windows上将不起作用.

One final note: this is not cross-platform, it will not work on Windows as it doesn't have select.poll and /dev/tty.

这篇关于在后台运行时,Python脚本会挂起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆