这个Python生产者 - 消费者无锁方法是否是线程安全的?

时间:2009-05-12 21:15:48

标签: python locking thread-safety producer-consumer

我最近编写了一个使用简单生产者/消费者模式的程序。它最初有一个与不正确使用线程有关的错误。我最终解决了这个问题。但它让我想到是否有可能以无锁的方式实现生产者/消费者模式。

我的案件要求很简单:

  • 一个生产者线程。
  • 一个消费者帖子。
  • 队列只有一个项目。
  • 生产者可以在消耗当前物品之前生成下一个物品。因此,当前项目丢失了,但这对我来说没问题。
  • 消费者可以在生成下一个项目之前使用当前项目。因此,当前项目被消耗了两次(或更多),但这对我来说没问题。

所以我写了这个:

QUEUE_ITEM = None

# this is executed in one threading.Thread object
def producer():
    global QUEUE_ITEM
    while True:
        i = produce_item()
        QUEUE_ITEM = i

# this is executed in another threading.Thread object
def consumer():
    global QUEUE_ITEM
    while True:
        i = QUEUE_ITEM
        consume_item(i)

我的问题是:这段代码是否是线程安全的?

立即评论:此代码并非真正无锁 - 我使用CPython并且它有GIL。

我测试了一点代码,它似乎工作。它转换为一些由于GIL而成为原子的LOAD和STORE操作。但我也知道,当x实现del x方法时,__del__操作不是原子操作。因此,如果我的项目有__del__方法并且发生了一些讨厌的调度,那么事情可能会中断。或者不是?

另一个问题是:为了使上述代码正常工作,我必须施加什么样的限制(例如生成的项目类型)?

我的问题只是关于利用CPython和GIL的怪癖的理论可能性,以便提出无锁(即没有锁代码,如代码中明确的线程。锁)。

6 个答案:

答案 0 :(得分:6)

Trickery会咬你。只需使用Queue在线程之间进行通信。

答案 1 :(得分:2)

是的,这将按您描述的方式运作:

  1. 生产者可以生产可跳过的元素。
  2. 消费者可能会使用相同的元素。
  3.   

    但我也知道,当x实现 del 方法时,del x操作不是原子操作。因此,如果我的项目有 del 方法并且发生了一些讨厌的调度,那么事情可能会中断。

    我在这里看不到“del”。如果在consume_item中发生del,那么 del 可能会出现在生产者线程中。我不认为这会是一个“问题”。

    不要打扰使用它。你将最终在无意义的轮询周期中耗尽CPU,并且它不比使用带锁的队列快,因为Python已经有了全局锁。

答案 2 :(得分:1)

这不是真正的线程安全,因为生产者可能会在消费者消费它之前覆盖QUEUE_ITEM,而消费者可能会消耗QUEUE_ITEM两次。如你所说,你没关系,但大多数人都不是。

对cpython内部知识有更多了解的人将不得不回答你更多的理论问题。

答案 3 :(得分:0)

我认为线程在生成/消费时可能会被中断,尤其是当项目是大对象时。 编辑:这只是一个疯狂的猜测。我不是专家。

此外,线程可能会在另一个项目开始运行之前生成/使用任意数量的项目。

答案 4 :(得分:0)

只要您坚持追加/弹出,就可以使用列表作为队列,因为两者都是原子的。

QUEUE = []

# this is executed in one threading.Thread object
def producer():
    global QUEUE
    while True:
        i = produce_item()
        QUEUE.append(i)

# this is executed in another threading.Thread object
def consumer():
    global QUEUE
    while True:
        try:
            i = QUEUE.pop(0)
        except IndexError:
            # queue is empty
            continue

        consume_item(i)

在如下所示的类范围内,您甚至可以清除队列。

class Atomic(object):
    def __init__(self):
        self.queue = []

    # this is executed in one threading.Thread object
    def producer(self):
        while True:
            i = produce_item()
            self.queue.append(i)

    # this is executed in another threading.Thread object
    def consumer(self):
        while True:
            try:
                i = self.queue.pop(0)
            except IndexError:
                # queue is empty
                continue

            consume_item(i)

    # There's the possibility producer is still working on it's current item.
    def clear_queue(self):
        self.queue = []

您必须通过查看生成的字节码来找出哪些列表操作是原子的。

答案 5 :(得分:0)

正如你所说,__del__可能是一个问题。如果只有一种方法可以阻止垃圾收集器在我们完成将新的__del__分配给QUEUE_ITEM之前调用旧对象上的increase the reference counter on the old object assign a new one to `QUEUE_ITEM` decrease the reference counter on the old object 方法,则可以避免这种情况。我们需要类似的东西:

{{1}}

我很害怕,但我不知道是否有可能。