需要验证:可清除的Python队列类

时间:2011-03-18 07:16:25

标签: python queue verification

由于我不是Python和多线程编程的专家,我想问你我的实现是否正确。

我的目标是扩展Queue类,以便清除它。并且应该返回已删除的项目。就这样。我的实现是:

import Queue

class ClearableQueue(Queue.Queue):

    def __init__(self, maxsize):
        Queue.Queue.__init__(self, maxsize)

    def clear(self):
        self.mutex.acquire()

        copyOfRemovedEntries = list(self.queue)
        self.queue.clear()
        self.unfinished_tasks = 0
        self.all_tasks_done.notifyAll()
        self.not_full.notifyAll()

        self.mutex.release()

        return copyOfRemovedEntries

这是对的吗? 谢谢。

更新:不幸的是,这个实现仍然不够,因为在调用clear()之后task_done可能抛出ValueError异常。

更准确地说:该队列被认为是在多线程环境中使用的。假设有一个生产者和一个工作者线程(但你也可以考虑更多的线程)。通常,如果工作线程调用get(),则应在worker完成其工作后调用task_done()。如果以这种方式发生这种情况,那么生成器线程可能会因为某种原因在工作线程调用get()之后和调用task_done()之前调用clear()。但是,到目前为止,如果工作线程要调用task_done(),则会抛出异常。这是因为task_done()通过检查Queue类的unfinished_tasks来检查未完成任务的数量。

如果只能通过ClearableQueue类处理这个问题,那么可以毫无后顾之忧地调用clear()方法。或者如果必须有某些不同的东西控制方法调用某种方式。

实际上,在我的具体情况下,我不使用join()方法,因此我不需要调用task_done()。但是,我想完成此功能。对其他人也有用。

2 个答案:

答案 0 :(得分:3)

如果查看the source,您将看到访问互斥锁的标准方法是在try:finally块中包含变异代码,以防出现问题:

import Queue

class ClearableQueue(Queue.Queue):

    def __init__(self, maxsize):
        Queue.Queue.__init__(self, maxsize)

    def clear(self):
        self.mutex.acquire()

        copyOfRemovedEntries = None
        try:
            copyOfRemovedEntries = list(self.queue)
            self.queue.clear()
            self.unfinished_tasks = 0
            self.all_tasks_done.notifyAll()
            self.not_full.notifyAll()
        finally:
            self.mutex.release()

        return copyOfRemovedEntries

修改1

如果您担心第二个线程在执行get()然后task_done()时丢失异常,为什么不将task_done()包装在try-catch块中?所有异常都告诉你,你是因为你已经确认了太多的项目,但是如果你的明确功能已经处理好了,问题出在哪里?

这会隐藏该异常,如果它困扰你,使函数的意图更明显,并删除我前面的例子中的双列表赋值:

class ClearableQueue(Queue.Queue):

    def __init__(self, maxsize):
        Queue.Queue.__init__(self, maxsize)

    def get_all(self)
        self.mutex.acquire()

        try:
            copyOfRemovedEntries = list(self.queue)
            self.queue.clear()
            self.unfinished_tasks = 0
            self.all_tasks_done.notifyAll()
            self.not_full.notifyAll()
        finally:
            self.mutex.release()

        return copyOfRemovedEntries

    def clear(self):
        self.get_all()

    def task_done(self):
        try:
            Queue.Queue.task_done(self)
        except ValueError:
            pass

修改2

这是一个更有效的解决方案,它不会隐藏任何东西:

class ClearableQueue(Queue.Queue):

    def __init__(self, maxsize):
        Queue.Queue.__init__(self, maxsize)
        self.tasks_cleared = 0

    def get_all(self)
        self.mutex.acquire()

        try:
            copyOfRemovedEntries = list(self.queue)
            self.queue.clear()
            self.unfinished_tasks = 0
            self.all_tasks_done.notifyAll()
            self.not_full.notifyAll()
            self.tasks_cleared += len(copyOfRemovedEntries)
        finally:
            self.mutex.release()

        return copyOfRemovedEntries

    def clear(self):
        self.get_all()

    def task_done(self):
        self.all_tasks_done.acquire()
        try:
            unfinished = self.unfinished_tasks + self.tasks_cleared - 1
            if unfinished <= 0:
                if unfinished < 0:
                    raise ValueError('task_done() called too many times')
                self.all_tasks_done.notify_all()
            self.unfinished_tasks = unfinished - self.tasks_cleared
            self.tasks_cleared = 0
        finally:
            self.all_tasks_done.release() 

我认为这应该避免异常,但仍然按照原始类的预期方式运行。

答案 1 :(得分:1)

你似乎正在遭受某种竞争条件,如果我理解,目前的情况是你有时会得到:

T1: |----->|------------->|-------------->|
    | get  |    some_opp  | task_done     |
T2: |---------->|------>|---------------->|
    | other_opp | clear | yet_another_opp |

gettask_done内执行明确的情况。这会导致崩溃。据我所知,你需要一些方法来做到这一点:

T1: |----->|------------->|-------------->|
    | get  |    some_opp  | task_done     |
T2: |---------->|------------------------>|------>|
    | other_opp | wait_for_task_done      | clear |

如果这是正确的,您可能需要第二个锁,由get_done设置get和释放,其中显示'此队列无法清除'。然后,您可能需要有一个版本的get和task_done,在您确实知道您正在做的特殊情况下不会执行此操作。

另一种方法是使用更多原子锁来执行此操作:

T1: |----->|------------------->|-------------->|------------->|
    | get  |    some_opp        | task_done     | finish_clear |
T2: |---------->|-------------->|---------------->|
    | other_opp | partial_clear | yet_another_opp |

你说'我没有完成这个任务,但你可以清除其余部分,然后告诉task_done该任务有一个被清除的尝试,所以它应该在之后做一些事情。但这开始变得相当复杂。