如何从Python中的主线程终止Producer-Consumer线程?

时间:2015-08-09 14:27:01

标签: python multithreading producer-consumer python-multithreading

我有 Producer Consumer 主题(threading.Thread),它们共享queue类型Queue。< / p>

制作人run

while self.running:
    product = produced() ### I/O operations
    queue.put(product)

消费者run

while self.running or not queue.empty():
    product = queue.get()
    time.sleep(several_seconds) ###
    consume(product)

现在我需要终止主线程中的两个线程,并且在终止之前要求queue必须为空(全部已消耗)。

目前,我使用以下代码终止这两个帖子:

主线程stop

producer.running = False
producer.join()
consumer.running = False
consumer.join()

但是,如果有更多的消费者,我认为这是不安全的。

此外,我不确定sleep是否会向生产者发布计划,以便生产更多产品。事实上,我发现生产者一直在挨饿#34;但我不确定这是否是根本原因。

有没有一个合适的方法来处理这个案子?

3 个答案:

答案 0 :(得分:5)

您可以将一个Sentinel对象放入队列以指示任务结束,从而导致所有使用者终止:

_sentinel = object()

def producer(queue):
    while running:
       # produce some data
       queue.put(data)
    queue.put(_sentinel)

def consumer(queue):
    while True:
        data = queue.get()
        if data is _sentinel:
            # put it back so that other consumers see it
            queue.put(_sentinel)
            break
        # Process data

此片段从Python Cookbook 12.3中无耻地复制。

  1. 使用_sentinel标记队列结束。如果生产者没有生成任务NoneNone也有效,但对于更一般的情况,使用_sentinel更安全。
  2. 对于每个消费者,您不需要将多个结束标记放入队列中。您可能不知道有多少线程正在消耗。当消费者找到它时,只需将哨兵放回队列,让其他消费者得到信号。

答案 1 :(得分:2)

编辑2:

a)消费者花费这么多时间的原因是因为即使您没有数据,您的循环也会持续运行。

b)我在底部添加了代码,说明了如何处理这个问题。

如果我理解正确,生产者/消费者是一个连续的过程,例如可以延迟关闭,直到退出当前的阻塞I / O并处理从中收到的数据。

在这种情况下,要以有序的方式关闭生产者和消费者,我会将主要线程的通信添加到生产者线程以调用关闭。在最一般的情况下,这可能是主线程可用于排队“关闭”代码的队列,但在单个生产者的简单情况下,要停止并且从不重新启动,它可能只是全局关闭标志。

您的生产者应该在它开始阻塞I / O操作之前检查其主循环中的此关闭条件(队列或标志)(例如,在您将其他数据发送到使用者队列之后)。如果设置了标志,那么它应该在队列上放置一个特殊的数据结尾代码(看起来不像普通数据),告诉消费者正在关闭,然后生产者应该返回(终止本身)。

应该修改使用者,以便在将数据从队列中拉出时检查此数据结束代码。如果找到了数据结尾代码,它应该按顺序关闭并返回(自行终止)。

如果有多个消费者,那么生产者可以在关闭之前为多个数据结束消息排队 - 每个消费者一个消息。由于消费者在阅读消息后停止消费,他们最终都将关闭。

或者,如果您事先不知道有多少消费者,那么消费者有序关闭的部分可能会重新排队数据末尾代码。

这将确保所有消费者最终看到数据结束代码并关闭,并且当完成所有操作后,队列中将有一个剩余项目 - 最后排队的数据结束代码消费者。

编辑:

表示数据结束代码的正确方法取决于应用程序,但在许多情况下,简单的None非常有效。由于None是一个单例,消费者可以使用非常有效的if data is None构造来处理最终案例。

在某些情况下可能更有效的另一种可能性是在主消费者循环外设置try /except ,这样如果发生了除外,那是因为您试图以一种始终有效的方式解压缩数据,除非您处理数据结束时代码。

编辑2:

将这些概念与您的初始代码相结合,现在生产者就是这样做的:

while self.running:
    product = produced() ### I/O operations
    queue.put(product)
for x in range(number_of_consumers):
    queue.put(None)  # Termination code

每位消费者都会这样做:

while 1:
    product = queue.get()
    if product is None:
        break
    consume(product)

主程序可以这样做:

producer.running = False
producer.join()
for consumer in consumers:
    consumer.join()

答案 2 :(得分:2)

您的代码中的一个观察是,您的consumer将继续寻找从队列中获取某些内容,理想情况下,您应该通过保留一些timeout并处理Empty异常来处理对于同样如下所示,理想情况下,这有助于检查每while self.running or not queue.empty() timeout

while self.running or not queue.empty():
    try:
        product = queue.get(timeout=1)
    except Empty:
        pass
    time.sleep(several_seconds) ###
    consume(product)

我确实模拟了您的情况并创建了producerconsumer个帖子,以下是与producers和4 consumers一起运行的示例代码工作得很好。希望这能帮到你!

import time
import threading

from Queue import Queue, Empty

"""A multi-producer, multi-consumer queue."""

# A thread that produces data
class Producer(threading.Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        threading.Thread.__init__(self, group=group, target=target, name=name,
                                  verbose=verbose)
        self.running = True
        self.name = name
        self.args = args
        self.kwargs = kwargs

    def run(self):
        out_q = self.kwargs.get('queue')
        while self.running:
            # Adding some integer
            out_q.put(10)
            # Kepping this thread in sleep not to do many iterations
            time.sleep(0.1)

        print 'producer {name} terminated\n'.format(name=self.name)


# A thread that consumes data
class Consumer(threading.Thread):

    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        threading.Thread.__init__(self, group=group, target=target, name=name,
                                  verbose=verbose)
        self.args = args
        self.kwargs = kwargs
        self.producer_alive = True
        self.name = name

    def run(self):
        in_q = self.kwargs.get('queue')

        # Consumer should die one queue is producer si dead and queue is empty.
        while self.producer_alive or not in_q.empty():
            try:
                data = in_q.get(timeout=1)
            except Empty, e:
                pass

            # This part you can do anything to consume time
            if isinstance(data, int):
                # just doing some work, infact you can make this one sleep
                for i in xrange(data + 10**6):
                    pass
            else:
                pass
        print 'Consumer {name} terminated (Is producer alive={pstatus}, Is Queue empty={qstatus})!\n'.format(
            name=self.name, pstatus=self.producer_alive, qstatus=in_q.empty())


# Create the shared queue and launch both thread pools
q = Queue()

producer_pool, consumer_pool = [], []


for i in range(1, 3):
    producer_worker = Producer(kwargs={'queue': q}, name=str(i))
    producer_pool.append(producer_worker)
    producer_worker.start()

for i in xrange(1, 5):
    consumer_worker = Consumer(kwargs={'queue': q}, name=str(i))
    consumer_pool.append(consumer_worker)
    consumer_worker.start()

while 1:
    control_process = raw_input('> Y/N: ')
    if control_process == 'Y':
        for producer in producer_pool:
            producer.running = False
            # Joining this to make sure all the producers die
            producer.join()

        for consumer in consumer_pool:
            # Ideally consumer should stop once producers die
            consumer.producer_alive = False

        break