无法从multiprocessing.Queue中获取.get()

时间:2014-12-09 17:05:56

标签: python queue multiprocessing gevent gipc

我正在构建一个Web应用程序,用于处理大约60,000个(并且不断增长的)大文件,执行一些分析并返回需要用户验证的“最佳猜测”。这些文件将按类别进行细化,以避免加载每个文件,但我仍然有一个场景,我可能需要一次处理1000多个文件。

这些是大文件,每个文件最多可能需要8-9秒才能处理,而在1000+文件情况下,让用户在评论之间等待8秒或者在文件处理之前等待2小时+是不切实际的。

为了解决这个问题,我决定使用多处理来生成几个工作程序,每个工作程序将从文件队列中选择,处理它们并插入到输出队列中。我有另一种方法,它基本上轮询项目的输出队列,然后当一个项目可用时将它们流式传输到客户端。

这很有效,直到队列任意停止返回项目的一部分时间。我们在我们的环境中使用gevent与Django和uwsgi,我知道在gevent环境中通过多处理创建子进程会在子进程中产生不希望的事件循环状态。在分叉之前产生的Greenlets在孩子中重复。因此,我决定使用gipc来协助处理子进程。

我的代码的示例(我无法发布我的实际代码):

import multiprocessing
import gipc
from item import Item

MAX_WORKERS = 10

class ProcessFiles(object):

    def __init__(self):
        self.input_queue = multiprocessing.Queue()
        self.output_queue = multiprocessing.Queue()
        self.file_count = 0

    def query_for_results(self):
        # Query db for records of files to process.
        # Return results and set self.file_count equal to
        # the number of records returned.
        pass

    # The subprocess.
    def worker(self):
        # Chisel away at the input queue until no items remain.
        while True:
            if self.no_items_remain():
                return

            item = self.input_queue.get(item)
            item.process()
            self.output_queue.put(item)

    def start(self):
        # Get results and store in Queue for processing
        results = self.query_for_results()
        for result in results:
             item = Item(result)
             self.input_queue.put(item)

        # Spawn workers to process files.
        for _ in xrange(MAX_WORKERS):
            process = gipc.start_process(self.worker)

        # Poll for items to send to client.
        return self.get_processed_items()

    def get_processed_items(self):

        # Wait for the output queue to hold at least 1 item.
        # When an item becomes available, yield it to client.
        count = 0
        while count != self.file_count:
            #item = self._get_processed_item()
            # Debugging:
            try:
                item = self.output_queue.get(timeout=1)
            except:
                print '\tError fetching processed item. Retrying...'
                continue

            if item:
                print 'QUEUE COUNT: {}'.format(self.output_queue.qsize())
                count += 1
                yield item
        yield 'end'

我希望输出在处理并产生一个项目后显示队列的当前计数:

QUEUE COUNT: 999
QUEUE COUNT: 998
QUEUE COUNT: 997
QUEUE COUNT: 996
...
QUEUE COUNT: 4
QUEUE COUNT: 3
QUEUE COUNT: 2
QUEUE COUNT: 1

但是,脚本只能设法在失败之前产生一些项目:

QUEUE COUNT: 999
QUEUE COUNT: 998
QUEUE COUNT: 997
QUEUE COUNT: 996
    Error fetching processed item. Retrying...
    Error fetching processed item. Retrying...
    Error fetching processed item. Retrying...
    Error fetching processed item. Retrying...
    Error fetching processed item. Retrying...
    Error fetching processed item. Retrying...
    ...

我的问题是:到底发生了什么?为什么我不能从队列中get?如何退回我期望的项目并避免这种情况?

1 个答案:

答案 0 :(得分:0)

无法获取项目时抛出的实际异常是什么?你是盲目地捕捉所有可能抛出的异常。另外,为什么不在没有超时的情况下使用get呢?你立刻再试一次,不做任何其他事情。可能只是调用一个块直到一个项目准备就绪。

关于这个问题,我认为发生的事情是gipc正在关闭与队列关联的管道,从而打破了队列。我希望抛出OSError而不是queue.Empty。有关详细信息,请参阅此bug report

作为替代方案,您可以使用进程池,在任何gevent内容发生之前启动池(这意味着您不必担心事件循环问题)。使用imap_unordered将作业提交到池中,您应该没问题。

您的启动功能类似于:

def start(self):
    results = self.query_for_results()
    return self.pool.imap_unordered(self.worker, results, 
        chunksize=len(results) // self.num_procs_in_pool)

@staticmethod
def worker(item):
    item.process()
    return item