Python 3.x 使python字典像队列一样线程安全

Python 3.x 使python字典像队列一样线程安全,python-3.x,Python 3.x,虽然我非常喜欢队列,但在编码时,我遇到了一个问题,如果我使用队列,就无法完成这个问题。基本上,我有一个生产者-工人体系结构,其中许多生产者在生产工作,许多工人在消费和加工工作。排队是一个自然的选择 但是,由于生成的作业数量巨大,我提出了一种解决方案,在作业仍在等待处理时对其进行批处理,并可能从队列中删除一些作业,以便队列中不会有很多项。例如,对于某些作业,时间戳已过期,不再需要处理。我希望在这些工作仍在等待处理时,从队列中剔除这些类型的结果,而不是让工人去弄清楚这一点(在我的情况下,这是很昂贵的

虽然我非常喜欢队列,但在编码时,我遇到了一个问题,如果我使用队列,就无法完成这个问题。基本上,我有一个生产者-工人体系结构,其中许多生产者在生产工作,许多工人在消费和加工工作。排队是一个自然的选择

但是,由于生成的作业数量巨大,我提出了一种解决方案,在作业仍在等待处理时对其进行批处理,并可能从队列中删除一些作业,以便队列中不会有很多项。例如,对于某些作业,时间戳已过期,不再需要处理。我希望在这些工作仍在等待处理时,从队列中剔除这些类型的结果,而不是让工人去弄清楚这一点(在我的情况下,这是很昂贵的,而且我无论如何也不能接触工人)

所以,由于我不能只从队列之间获取项目,所以我决定将结构从队列更改为字典(不能使用列表,因为出于某种原因我需要密钥/值对),并以一种与队列行为完全相同的方式实现dict。这是我的任务清单:

  • 新的dict必须有push、pop方法,就像queue一样
  • 新的dict必须是线程安全的。因为很多工人都在消费它们,我们不想重复同样的结果。有了线程安全,我可以肯定只有一个工人会接受这项工作。此外,线程安全意味着字典一次只由一个使用者进行变异,因此所有更改本质上都是原子性的
  • 如果dict为空,则新dict的pop()方法必须等待新作业到达,就像python队列中的get()方法一样,如果队列为空,它将等待项目到达
  • 所以我去实现了下面的代码。请注意lock.acquire()的用法

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

    在else条件下,如果用户不在dict中,则正在创建新记录-我不希望一个工作人员创建新密钥,同时,另一个工作人员也创建相同的密钥,并且该新密钥覆盖旧密钥,从而丢失数据。我知道这是一个非常小的错误窗口,如果您认为它实际上等于零,我准备从push()方法中删除锁

    我很有可能过于愚蠢,错过了一些要点,但我现在想不出任何事情。我感谢你花时间解决这个问题。:)

    编辑: 所以根据@Kevin所说,这是我对“条件”的看法


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

    在这里使用锁可能不是最好的选择。相反,在
    pop()
    方法中创建一个和
    .wait()。然后,在
    put()
    中调用
    .notify()
    (在最后,就在您释放之前,但不是在最后,否则您可能会意外地将锁锁定)。

    很抱歉,我花了一些时间才理解这个概念。我用新代码编辑了我的问题。你能看一下吗?我不明白为什么你把循环条件下放到了
    if
    中,但在我看来它是合理的。我还建议将
    条件.notify()
    调用移动到
    尝试的末尾,而不是最后
    的开头。谢谢…既然我理解了这个概念,我可以做出适当的更改…)
    
    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()
    
    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()