【问题标题】:In Django, how to call a subprocess with a slow start-up time在Django中,如何调用启动时间慢的子进程
【发布时间】:2010-11-28 13:43:18
【问题描述】:

假设您在 Linux 上运行 Django,并且您有一个视图,并且您希望该视图从名为 cmd 的子进程返回数据,该子进程运行在视图创建的文件上,例如 likeso:

 def call_subprocess(request):
     response = HttpResponse()

     with tempfile.NamedTemporaryFile("W") as f:
         f.write(request.GET['data']) # i.e. some data

     # cmd operates on fname and returns output
     p = subprocess.Popen(["cmd", f.name], 
                   stdout=subprocess.PIPE, 
                   stderr=subprocess.PIPE)

     out, err = p.communicate()

     response.write(p.out) # would be text/plain...
     return response

现在,假设 cmd 的启动时间很慢,但运行时间却很快,而且它本身没有守护程序模式。我想改进这个视图的响应时间。

我想通过在工作池中启动多个 cmd 实例来让整个系统运行得更快,让它们等待输入,并让 call_process 要求其中一个工作池进程处理数据。

这实际上是两部分:

第 1 部分。调用 cmdcmd 的函数等待输入。这可以通过管道来完成,即

def _run_subcmd():
    p = subprocess.Popen(["cmd", fname], 
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    out, err = p.communicate()
    # write 'out' to a tmp file
    o = open("out.txt", "W")
    o.write(out)
    o.close()
    p.close()
    exit()

def _run_cmd(data):
    f = tempfile.NamedTemporaryFile("W")
    pipe = os.mkfifo(f.name)

    if os.fork() == 0:
        _run_subcmd(fname)
    else:
        f.write(data)

    r = open("out.txt", "r")
    out = r.read()
    # read 'out' from a tmp file
    return out

def call_process(request):
    response = HttpResponse()

    out = _run_cmd(request.GET['data'])

    response.write(out) # would be text/plain...
    return response

第 2 部分。一组在后台运行并等待数据的工作人员。即我们想要扩展上述内容,以便子进程已经在运行,例如当 Django 实例初始化或第一次调用这个 call_process 时,会创建一组这些工作人员

WORKER_COUNT = 6
WORKERS = []

class Worker(object):
    def __init__(index):
        self.tmp_file = tempfile.NamedTemporaryFile("W") # get a tmp file name
        os.mkfifo(self.tmp_file.name)
        self.p = subprocess.Popen(["cmd", self.tmp_file], 
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        self.index = index

    def run(out_filename, data):
        WORKERS[self.index] = Null # qua-mutex??
        self.tmp_file.write(data)
        if (os.fork() == 0): # does the child have access to self.p??
            out, err = self.p.communicate()
            o = open(out_filename, "w")
            o.write(out)
            exit()

        self.p.close()
        self.o.close()
        self.tmp_file.close()
        WORKERS[self.index] = Worker(index) # replace this one
        return out_file

    @classmethod
    def get_worker() # get the next worker
    # ... static, incrementing index 

应该在某处对工作人员进行一些初始化,如下所示:

def init_workers(): # create WORKERS_COUNT workers
    for i in xrange(0, WORKERS_COUNT):
        tmp_file = tempfile.NamedTemporaryFile()
        WORKERS.push(Worker(i))

现在,我上面的内容变成了这样:

def _run_cmd(data):
     Worker.get_worker() # this needs to be atomic & lock worker at Worker.index

     fifo = open(tempfile.NamedTemporaryFile("r")) # this stores output of cmd

     Worker.run(fifo.name, data)
     # please ignore the fact that everything will be
     # appended to out.txt ... these will be tmp files, too, but named elsewhere.

     out = fifo.read()
     # read 'out' from a tmp file
     return out


def call_process(request):
     response = HttpResponse()

     out = _run_cmd(request.GET['data'])

     response.write(out) # would be text/plain...
     return response

现在,问题:

  1. 这行得通吗? (我刚刚把这个从头顶输入到 StackOverflow 中,所以我确信存在问题,但从概念上讲,它会起作用)

  2. 要寻找哪些问题?

  3. 有没有更好的替代方案?例如线程可以正常工作吗(它是 Debian Lenny Linux)?有没有像这样处理并行进程工作池的库?

  4. 我应该注意与 Django 的交互吗?

感谢阅读!我希望你和我一样觉得这个问题很有趣。

布赖恩

【问题讨论】:

    标签: python django multithreading fork subprocess


    【解决方案1】:

    我似乎在抨击这个产品,因为这是我第二次回复推荐这个产品。

    但您似乎需要消息队列服务,尤其是分布式消息队列。

    它是如何工作的:

    1. 您的 Django 应用程序请求 CMD
    2. CMD 被添加到队列中
    3. CMD 被推送到多个作品中
    4. 在上游执行并返回结果

    大部分代码都存在,您不必构建自己的系统。

    看看最初用 Django 构建的 Celery。

    http://www.celeryq.org/ http://robertpogorzelski.com/blog/2009/09/10/rabbitmq-celery-and-django/

    【讨论】:

    • 这很有趣 - 我会调查的。但是,我可能(或可能没有)遇到的问题是第 4 步的第 1 部分(“它已执行”,即启动 LaTeX)必须在第 2 步之前发生(“CMD 被添加到队列”,即 LaTeX 获取数据)。然而,我非常有信心 Celery 可以做到这一点——但这需要一些钻研。
    【解决方案2】:

    Issy 已经提到过 Celery,但由于 cmets 不能很好地工作 有代码示例,我将作为答案回复。

    您应该尝试将 Celery 与 AMQP 结果存储同步使用。 您可以将实际执行分配给另一个进程甚至另一台机器。在 celery 中同步执行很简单,例如:

    >>> from celery.task import Task
    >>> from celery.registry import tasks
    
    >>> class MyTask(Task):
    ...
    ...     def run(self, x, y):
    ...         return x * y 
    >>> tasks.register(MyTask)
    
    >>> async_result = MyTask.delay(2, 2)
    >>> retval = async_result.get() # Now synchronous
    >>> retval 4
    

    AMQP 结果存储使得返回结果的速度非常快, 但它仅在当前的开发版本中可用(在代码冻结中成为 0.8.0)

    【讨论】:

    • 感谢 Asksol。一项要求是让任务作为守护程序永远运行,然后只从它发送/接收数据。 LaTeX 必须在调用 run() 之前运行(否则您必须等待 LaTeX 启动,这会排除使用任务队列的全部目的)。我正在研究 Celery,看看它是否可以做到这一点(我希望它可以)。
    • 除了优化之外,我没有看到 LaTeX 必须运行的要求?为此,您必须使用 LaTeX C API(或其他任何东西)在工作进程中运行它。这应该是可能的,但需要您大量定制芹菜。这可能是一个很好的起点,因为它已经解决了您的部分问题。我并不是说任务队列非常适合这个,但分布式/并行处理部分可能是。您想要一个任务池并且想要发送/接收结果,您只希望工作进程成为 LaTeX 处理器。
    【解决方案3】:

    如何使用python-daemon 或其继任者grizzled“守护”子进程调用。

    【讨论】:

    • 不相关,但是,它在什么方面做得更好?我遇到了一些与 python-daemon 相关的问题,但我天生对集合库持怀疑态度。
    猜你喜欢
    • 2011-08-04
    • 1970-01-01
    • 2014-08-10
    • 1970-01-01
    • 2016-01-17
    • 2013-08-06
    • 2012-02-27
    • 2011-05-19
    • 1970-01-01
    相关资源
    最近更新 更多