使一个python字典线程安全像队列

时间:2014-11-05 21:23:03

标签: python-3.x

虽然我喜欢排队,但编码时遇到的问题是如果我使用队列则无法完成。基本上我有一个生产者 - 工人体系结构,许多生产者正在生产工作,许多工人正在消费和处理它们。队列是一个自然的选择。

但是,由于生成了大量的作业,我提出了解决方案,在他们仍在等待处理时批量处理作业,并且可能从队列中清除其中一些作业,以便队列将没有很多东西。例如。对于某些作业,时间戳已过期,不再需要处理它们。而不是工人来解决这个问题(这在我的情况下是昂贵的,而且我无论如何都无法触及工人),我想从队列中清除这些类型的结果,而这些工作仍在等待处理。

因为我不能只从中间队列中获取项目,所以我决定将体系结构从队列更改为字典(不能使用列表,因为我出于某种原因需要键/值对)并且以这种方式实现dict它的行为与队列完全一样。所以这是我的待办事项列表:

  1. 新的dict必须有push,pop方法,就像排队一样。
  2. 新词典必须是线程安全的。因为许多工人正在消费它们,我们不想两次处理相同的结果。通过线程安全,我可以肯定只有一个工作人员会接受这项工作。另外,线程安全意味着字典一次只被一个消费者变异,因此所有变化都是原子性的。
  3. 新dict的pop()方法必须等待新作业到达,以防dict为空,就像python队列中的get()方法一样,如果队列等待项目到达是空的。
  4. 所以我去实现了下面的代码。请注意lock.acquire()

    的使用
    def put(self, userId,  job):
        self.__lock.acquire()
    
        try:
            if userId in self.__queue:
                #merge the 'job' and the dict already present in 'self.__queue[userId]'
            else:
                self.__queue[userId] = job
        finally:
            self.__lock.release()
    
    def pop(self, wait = True):
        self.__lock.acquire()
    
        try:
            if wait:
                while(len(self.__queue) == 0):
                    pass
    
            #pop the 'first' value in dict.
            #Please do not worry that dict has no first or last value.
            #I sort the dict keys, and pop the first value.
        finally:
            self.__lock.release()
    

    现在注意pop()方法。假设dict中没有数据,因此其中一个worker将持有锁,其他worker将等待锁释放。但是,push()方法也使用相同的锁,因此它是一个死锁情况。没有数据甚至会进入dict,因为pop()持有锁。 但是如果我从push方法()中移除锁定,以便数据可以随时出现,我担心在工人的竞争条件下覆盖一些数据。请参阅下面的示例。

    在else条件下,如果该用户不在dict中,则创建新记录 - 我不希望一个worker创建新密钥,同时另一个worker也创建相同的密钥,这个新密钥会覆盖旧密钥,因此我会丢失数据。我理解它是一个非常小的错误窗口,如果你认为它实际上等于零,我准备完全从push()方法中删除锁。

    我有一个很大的机会,我过于愚蠢而且错过了一些观点,但我现在想不出任何事情。感谢您抽出宝贵时间来解决这个问题。 :)

    编辑:

    所以根据@Kevin的说法,这是我的版本"条件"。

    def put(self, userId,  job):
        self.__condition.acquire()
        try:
            if userId in self.__queue:
                #merge the 'job' and the dict already present in 'self.__queue[userId]'
            else:
                self.__queue[userId] = job
        finally:
            self.__condition.notify()
            self.__condition.release()
    
    def pop(self, wait = True):
        self.__condition.acquire()
    
        try:
            if wait:
                while True:
                    if(len(self.__queue) > 0):
                        break
                    self.__condition.wait()
    
            #pop the 'first' value in dict.
            #Please do not worry that dict has no first or last value.
            #I sort the dict keys, and pop the first value.
        finally:
            self.__condition.release()
    

    这是我第一次有条件,因此我将我的代码放在这里,以便其他人可以验证。谢谢。

1 个答案:

答案 0 :(得分:0)

在这里使用锁可能不是最好的选择。而是在.wait()方法(您当前拥有pop())中为其创建conditionpass。然后,在.notify()中调用put()(最后,在您发布之前,但不在最后,或者您不小心将锁定锁定)。