【问题标题】:Queues and Threading: subprocess goes missing队列和线程:子进程丢失
【发布时间】:2014-06-01 12:59:26
【问题描述】:

早上,

我正在编写一个需要将视频转换为 .mp4 格式的 python 守护程序。 为此,我计划通过 Subprocess 使用 Handbrake,但我得到的结果好坏参半:有时它有效,有时该进程甚至没有显示在顶部。

我不确定发生了什么。我尝试了一些变体,例如使用 Shell=True,但问题仍然存在。

谢谢,

#! /usr/bin/python
# -*- coding: utf-8 -*- 
import os, time, threading, psutil, resource, logging, subprocess as sp, sys
from Queue import Queue
from threading import Thread

SERVER_LOG=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'convertCentral.log')
logging.basicConfig(format='[%(asctime)s.%(msecs).03d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filename=SERVER_LOG, level=logging.INFO)

class QueueServer(object):

    current_video_queue = Queue(maxsize=0)
    N_WORKER_THREADS = 1
    counter = 0


    def __init__(self):
        print("[QueueServer] Initializing the video conversion queue")
        t = threading.Thread(target=self.monitor)
        t.start()


    ''' Alters the process' niceness in order to throtle the CPU usage '''
    def preexec_fn(self):
        pid = os.getpid()
        ps = psutil.Process(pid)
        ps.set_nice(10)
        resource.setrlimit(resource.RLIMIT_CPU, (1, 1))


    ''' Converts the video using Handbrake via subprocess'''
    def convertVideo(self, video):
        print("Now converting %s" % video)
        fileName, fileExtension = os.path.splitext(video)
        payload = "ulimit -t 360; nice -n 15 HandBrakeCLI -i %s -e x264 -q 15 -o %s.mp4" % (video, fileName)
        payload = payload.split(" ")

        # Fire in the hole
        pr = sp.Popen(payload, stdout=open('/dev/null', 'w'), stderr=sp.STDOUT)
        print("Fired.")
        pr.wait()

        self.counter = self.counter + 1
        print("Conversion's done. %d" % self.counter)


    ''' A worker thread '''
    def worker(self):
        while True:
            print("Getting one")
            item = self.current_video_queue.get()
            print("Firing conversion: %s" % item)
            self.convertVideo(item)
            self.current_video_queue.task_done()
            print("All done")


    def monitor(self):
        for i in range(self.N_WORKER_THREADS):
            print("Firing thread")
            t = Thread(target=self.worker)
            t.daemon = True
            t.start()


    ''' Adds a video to the video conversion queue '''
    def add(self, video):
        print("* Adding %s  to the queue" % video)
        self.current_video_queue.put(video)
        print("* Added %s  to the queue" % video)


q = QueueServer()
q.add('UNKNOWN_PARAMETER_VALUE.WMV')
#time.sleep(500)

这是我在日志中得到的:

Hal@ubuntu:~/Desktop/$ python threadedqueue.py
[QueueServer] Initializing the video conversion queue
Firing thread
* Adding UNKNOWN_PARAMETER_VALUE.WMV  to the queue
* Added UNKNOWN_PARAMETER_VALUE.WMV  to the queue
Getting one
Firing conversion: UNKNOWN_PARAMETER_VALUE.WMV
Now converting UNKNOWN_PARAMETER_VALUE.WMV

所以,我们可以看出子进程确实在运行,但它只是神秘地死在那里...... 有什么想法可能导致这种情况吗?

【问题讨论】:

    标签: python multithreading video subprocess video-processing


    【解决方案1】:

    首先,您提供的有效负载可能不是您想要的。

    在正常操作中,子进程不会将您的命令发送给 shell,而是启动名称位于 args[0] 的进程,并将所有其他参数直接传递给它。您正在做的是传递以下参数:

    ["-t", "360;", "nice", "-n", "15", "HandBrakeCLI", "-i", "someInput", "-e", "x264", "-q", "15", "-o", "someOutput.mp4"]
    

    ... 到一个 非常 混乱的 ulimit 进程。 相反,您想要的是使用 shell=True,并提供 args 作为字符串。这告诉 Popen,而不是实际启动您请求的进程,而是启动一个 shell 并将其全部转储为单行。

    payload = "ulimit -t 360; nice -n 15 HandBrakeCLI -i %s -e x264 -q 15 -o %s.mp4" % (video, fileName)
    pr = sp.Popen(payload, shell=True, stdout=sp.DEVNULL, stderr=sp.STDOUT)
    print('Started handbrake')
    pr.wait()
    

    如果您没有打印出“Started handbrake”(已启动手刹),那么您可能会认为 Popen 出现了严重错误。调试它的一个好方法是获取有效负载,导入子进程并尝试在交互式控制台上使用 Popen 打开它。如果您被锁定在 Popen 调用中,则 ctrl-c 应该可以跟踪您在其中挂起的位置,这反过来可能会为您提供一些帮助。即使它对你来说毫无意义,它也可以帮助其他人了解正在发生的事情。

    希望对你有帮助!

    【讨论】:

    • 您好,感谢您的帮助!我设置了 shell=True 参数并对其进行了测试,结果又好坏参半……但是,如果我在交互式控制台中运行子进程 sn-p,它可以完美运行!好像跟队列有关系。
    • 好吧,队列会让您无法获取作业 - 您的输出看起来像是获取作业,但在 Popen 上“暂停”。它是否曾经打印过“Started handbrake”/“Fired”。与新的 Popen 通话?如果没有,您可以进行 printf 样式的调试(到处打印输出),或者在最后一个“好”行之后使用import pdb; pdb.set_trace() 启动 Python 调试器,这将允许您单步执行代码(下一步转到下一步line, step 应该进入一个函数...我认为它有一个帮助页面。)这应该可以让你看到事情在哪里暂停/中断。
    • 在 q.add 行之后添加 time.sleep(5) 解决了这个问题。
    • 虽然这部分已经解决了,但我仍然在为守护进程的其余部分苦苦挣扎。显示所有消息,但 pr.wait() 是瞬时的并且失败。我想我需要为此提出另一个问题
    • 注意:ulimit 是一个内置的 shell 命令,也就是说,如果没有 shell=True,即使没有其他未知参数,它也会失败。
    【解决方案2】:

    您可以在 Python 中模拟 ulimit -tnice -n 以在没有 shell 的情况下运行 HandBrakeCLI

    #!/usr/bin/env python
    import os
    import resource
    import shlex
    from subprocess import call, STDOUT
    
    DEVNULL = open(os.devnull, 'wb', 0)
    
    # quote filenames to allows names with spaces
    path, output_path = map(shlex.quote, [video, filename + '.mp4'])
    cmd = "HandBrakeCLI -i {path} -e x264 -q 15 -o {output_path}".format(**vars())
    
    def limit_resources():
        resource.setrlimit(resource.RLIMIT_CPU, (360, 360)) # emulate ulimit -t
        os.nice(15) # emulate nice -n
    
    rc = call(shlex.split(cmd), stdout=DEVNULL, stderr=STDOUT,
              preexec_fn=limit_resources)
    

    【讨论】:

    • 我会在星期一试一试,因为我现在不在家,但我不认为 ulimit 和 nice 是问题所在。无论如何,我稍后会给你一些反馈。谢谢!
    • @Hal:我也认为ulimit, nice 不是您代码中的唯一 问题。我应该明确提到它。 1. 确保代码在单线程中工作,例如,在引入多个线程之前检查RLIMIT_CPU 是否在您的系统上工作 2. 始终检查rc != 0(如果可以使用check_call() HandBrakeCLI 失败时的异常) 3. 您可以在单个线程中运行所有代码,因为Popen() 不会阻塞您的主线程(尽管您可以使用多个线程来方便)
    猜你喜欢
    • 1970-01-01
    • 2011-09-27
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 2013-11-15
    • 2019-03-11
    • 1970-01-01
    相关资源
    最近更新 更多