我有 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;但我不确定这是否是根本原因。
有没有一个合适的方法来处理这个案子?
答案 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中无耻地复制。
_sentinel
标记队列结束。如果生产者没有生成任务None
,None
也有效,但对于更一般的情况,使用_sentinel
更安全。答案 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)
我确实模拟了您的情况并创建了producer
和consumer
个帖子,以下是与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