Проблема производителя/потребителя с многопроцессорной обработкой python

Я пишу серверную программу с одним производителем и несколькими потребителями, меня смущает только первая задача, поставленная в очередь потребляемых, после чего задачи, оставшиеся в очереди, больше не потребляются, они остаются в очереди навсегда.

from multiprocessing import Process, Queue, cpu_count
from http import httpserv
import time

def work(queue):
    while True:
        task = queue.get()
        if task is None:
            break
        time.sleep(5)
        print "task done:", task
    queue.put(None)

class Manager:
    def __init__(self):
        self.queue = Queue()
        self.NUMBER_OF_PROCESSES = cpu_count()

    def start(self):
        self.workers = [Process(target=work, args=(self.queue,))
                        for i in xrange(self.NUMBER_OF_PROCESSES)]
        for w in self.workers:
            w.start()

        httpserv(self.queue)

    def stop(self):
        self.queue.put(None)
        for i in range(self.NUMBER_OF_PROCESSES):
            self.workers[i].join()
        queue.close()

Manager().start()

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

P.S. Еще два вопроса, не связанные с вышеизложенным, я не уверен, лучше поставить HTTP-сервер в свой собственный процесс, отличный от основного процесс, если да, как я могу заставить основной процесс работать до того, как все процесс детей заканчивается. Второй вопрос: какой лучший способ остановить HTTP-сервер изящно?

Изменить: добавьте код производителя, это просто простой сервер wsgi python:

import fapws._evwsgi as evwsgi
from fapws import base

def httpserv(queue):
    evwsgi.start("0.0.0.0", 8080)
    evwsgi.set_base_module(base)

    def request_1(environ, start_response):
        start_response('200 OK', [('Content-Type','text/html')])
        queue.put('task_1')
        return ["request 1!"]

    def request_2(environ, start_response):
        start_response('200 OK', [('Content-Type','text/html')])
        queue.put('task_2')
        return ["request 2!!"]

    evwsgi.wsgi_cb(("/request_1", request_1))
    evwsgi.wsgi_cb(("/request_2", request_2))

    evwsgi.run()

Ответ 1

Я думаю, что должно быть что-то не так с частью веб-сервера, так как это прекрасно работает:

from multiprocessing import Process, Queue, cpu_count
import random
import time


def serve(queue):
    works = ["task_1", "task_2"]
    while True:
        time.sleep(0.01)
        queue.put(random.choice(works))


def work(id, queue):
    while True:
        task = queue.get()
        if task is None:
            break
        time.sleep(0.05)
        print "%d task:" % id, task
    queue.put(None)


class Manager:
    def __init__(self):
        self.queue = Queue()
        self.NUMBER_OF_PROCESSES = cpu_count()

    def start(self):
        print "starting %d workers" % self.NUMBER_OF_PROCESSES
        self.workers = [Process(target=work, args=(i, self.queue,))
                        for i in xrange(self.NUMBER_OF_PROCESSES)]
        for w in self.workers:
            w.start()

        serve(self.queue)

    def stop(self):
        self.queue.put(None)
        for i in range(self.NUMBER_OF_PROCESS):
            self.workers[i].join()
        self.queue.close()


Manager().start()

Пример вывода:

starting 2 workers
0 task: task_1
1 task: task_2
0 task: task_2
1 task: task_1
0 task: task_1

Ответ 2

"Второй вопрос: какой лучший способ остановить HTTP-сервер изящно?"

Это сложно.

У вас есть два варианта для Interprocess Communication:

  • Элементы управления вне диапазона. У сервера есть другой механизм связи. Другой сокет, сигнал Unix или что-то еще. Что-то еще может быть файлом "stop-now" в локальном каталоге сервера. Кажется нечетным, но он работает хорошо и проще, чем введение цикла выбора для прослушивания в нескольких сокетах или обработчике сигнала, чтобы поймать сигнал Unis.

    Файл "stop-now" прост в реализации. Цикл evwsgi.run() просто проверяет этот файл после каждого запроса. Чтобы остановить сервер, вы создаете файл, выполняете запрос /control (который получит ошибку 500 или что-то в этом роде, на самом деле это не имеет значения), и сервер остановится. Не забудьте удалить файл stop-now, иначе ваш сервер не будет перезагружен.

  • Внутриполосные элементы управления. У сервера есть другой URL (/stop), который остановит его. Поверхностно это похоже на кошмар безопасности, но полностью зависит от того, где и как этот сервер будет использоваться. Поскольку он выглядит как простая оболочка вокруг внутренней очереди запросов, этот дополнительный URL-адрес работает хорошо.

    Чтобы выполнить эту работу, вам нужно написать собственную версию evwsgi.run(), которая может быть прервана, установив некоторую переменную таким образом, который выйдет из цикла.

Edit

Вероятно, вы не хотите прекращать работу своего сервера, так как вы не знаете состояние его рабочих потоков. Вам нужно сигнализировать сервер, а затем вам просто нужно подождать, пока оно не закончится нормально.

Если вы хотите принудительно убить сервер, то os.kill() (или multiprocessing.terminate) будет работать. Кроме того, конечно, вы не знаете, что делали дочерние потоки.