如何使Queue子类的putall和getall方法成为原子?

时间:2015-02-12 17:28:39

标签: python multithreading queue

我有一个继承自Queue的类,并由另一个工厂类使用:

class myQueue(Queue):
    def putall(self , mobjects , *args):
        self.put(mobject, *args )

    def getall(self , number , *args ):
        return [self.get(*args) for _ in xrange(number)]

我在外部使用putallgetall,并希望它们是原子的。我怎样才能做到这一点?我使用的是Python 2.6。

1 个答案:

答案 0 :(得分:1)

为了使putallgetall成为原子,您需要持有一个锁,以防止任何其他修改Queue的方法运行。显而易见的选择是使用mutex内部使用的Queue对象,但这是使用threading.Lock实现的,这意味着它不能递归地使用。这意味着天真的解决方案将导致死锁:

from Queue import Queue
import threading

class myQueue(Queue):
    def putall(self , objects , *args):
        with self.mutex:
            for object in objects:
                self.put(object, *args) # This will hang.

    def getall(self , number , *args ):
        with self.mutex:
            return [self.get(*args) for _ in xrange(number)] # This will hang

q = myQueue()
q.putall(["1", "2", "3"])
print(q.getall(2))

这会在putallgetall内陷入僵局,因为两者都会尝试递归获取mutexputall / getall中的第一个,然后再次{ {1}} / put)。你有几个选择来解决这个问题。最简单的方法是使用get覆盖mutex内创建的Queue.__init__实例。这还需要重新创建使用threading.RLock

构建的其他一些threading.Condition个对象
mutex

输出:

from Queue import Queue
import threading

class myQueue(Queue):
    def __init__(self, *args, **kwargs):
        Queue.__init__(self, *args, **kwargs)
        self.mutex = threading.RLock()
        self.not_empty = threading.Condition(self.mutex)
        self.not_full = threading.Condition(self.mutex)
        self.all_tasks_done = threading.Condition(self.mutex)

    def putall(self , objects , *args):
        with self.mutex:
            for object in objects:
                self.put(object, *args)

    def getall(self , number , *args ):
        with self.mutex:
            return [self.get(*args) for _ in xrange(number)]

q = myQueue()
q.putall(["1", "2", "3"])

print(q.getall(2))

请注意,您在这里依赖于['1', '2'] 的实现细节,如果在新版本的Python中实现更改,这会使这种情况更容易破解。更加面向未来的Queue版本将包装myQueue实例,为其所有需要互斥锁的公共方法提供实现,并使用您自己的递归锁来同步这些方法。