В Django, как вызвать подпроцесс с медленным временем запуска

StackOverflow https://stackoverflow.com/questions/1428900

Вопрос

Предположим, вы запускаете Django в Linux, и у вас есть представление, и вы хотите, чтобы это представление возвращало данные из подпроцесса, вызываемого 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 в рабочем пуле пусть они ожидают ввода, и, имея вызов_процесса попросите один из этих процессов рабочего пула обрабатывать данные.

Это действительно 2 части:

Часть 1.Функция , которая вызывает cmd и cmd ожидает ввода.Это можно было бы сделать с помощью труб, т. е.

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 инициализируется, или это вызов_процесса при первом вызове создается набор этих рабочих

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 

Где-то должна быть какая-то инициализация workers, например, так:

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, о которых я должен осознавать?

Спасибо за чтение!Я надеюсь, что вы находите эту проблему такой же интересной, как и я.

Брайан

Это было полезно?

Решение

Может показаться, что я отказываюсь от этого продукта, поскольку это уже второй раз, когда я отвечаю с подобной рекомендацией.

Но, похоже, вам нужна служба очереди сообщений, в частности распределенная очередь сообщений.

вот как это будет работать:

  1. Ваше приложение Django запрашивает CMD
  2. CMD добавляется в очередь
  3. CMD получает доступ к нескольким работам
  4. Он выполняется, и результаты возвращаются вверх по течению

Большая часть этого кода уже существует, и вам не нужно заниматься созданием своей собственной системы.

Взгляните на Celery, который изначально был создан с помощью Django.

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

Другие советы

Исси уже упоминал Сельдерей, но поскольку комментарии плохо работают с примерами кода я отвечу вместо этого.

Вам следует попробовать использовать Celery синхронно с хранилищем результатов AMQP.Вы могли бы распространить фактическое выполнение на другой процесс или даже другую машину.Выполнять синхронное выполнение в сельдерее легко, например:

>>> 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 позволяет очень быстро отправлять результат обратно, но оно доступно только в текущей версии разработки (в code-freeze, чтобы стать 0.8.0)

Как насчет "демонизации" вызова подпроцесса с помощью python-демон или его преемник, поседевший.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top