В Django, как вызвать подпроцесс с медленным временем запуска - PullRequest
4 голосов
/ 15 сентября 2009

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

Это действительно 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 или при первом вызове 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. Есть ли взаимодействия с Джанго, о которых я должен знать?

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

Brian

Ответы [ 3 ]

3 голосов
/ 16 сентября 2009

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

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

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

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

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

Взгляните на сельдерей, который изначально был построен с Джанго.

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

3 голосов
/ 18 сентября 2009

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

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

>>> 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)

0 голосов
/ 16 сентября 2009

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

...