由于我不是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()。但是,我想完成此功能。对其他人也有用。
答案 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 |
在get
和task_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该任务有一个被清除的尝试,所以它应该在之后做一些事情。但这开始变得相当复杂。