告诉消费者停止等待队列元素

时间:2013-09-22 12:50:50

标签: python multithreading multiprocess

这是一个例子。我有一个生产者和几个消费者。

#!/usr/bin/env python2

from multiprocessing import Process, Queue
import time

def counter(low, high):
    current = low 
    while current <= high:
        yield current
        current += 1

def put_tasks(q):
    for c in counter(0, 9):
        q.put(c)
        time.sleep(.1)
    print('put_tasks: no more tasks') 

def work(id, q): 
    while True:
        task = q.get()
        print('process %d: %s' % (id, task))
        time.sleep(.3)
    print('process %d: done' % id) 

if __name__ == '__main__':
    q = Queue(2)
    task_gen = Process(target=put_tasks, args=(q,))
    processes = [Process(target=work, args=(id, q)) for id in range(0, 3)] 

    task_gen.start()
    for p in processes:
        p.start()
    for p in processes:
        p.join()

counter只是put_tasks的数字生成器。通常,我会有几千个任务而不是像本例中的10个任务。这段代码的重点是逐步向队列提供任务。

问题在于消费者无法预先知道他们将要处理多少任务,但put_tasks函数确实知道它何时完成(然后打印no more tasks)。

示例输出:

process 2: 0
process 0: 1
process 1: 2
process 2: 3
process 0: 4
process 1: 5
process 2: 6
process 0: 7
process 1: 8
process 2: 9
put_tasks: no more tasks

所有任务都会被处理,但程序会挂起(每个进程都会卡在q.get()上。我希望它在处理完所有任务时终止而不会牺牲速度或安全性(没有丑陋的超时)。

有什么想法吗?

3 个答案:

答案 0 :(得分:3)

最简单的方法是向队列添加一些告诉消费者所有工作已完成的内容。

number_of_consumers = 3

def put_tasks(q):
    for c in counter(0, 9):
        q.put(c)
        time.sleep(.1)
    print('put_tasks: no more tasks')
    for i in range(number_of_consumers):
        q.put(None)

def work(id, q): 
    while True:
        task = q.get()
        if task is None:
            break
        print('process %d: %s' % (id, task))
        time.sleep(.3)
    print('process %d: done' % id) 

答案 1 :(得分:3)

我建议将一个sentinel值放在队列的末尾

def put_tasks(q):
    ...

    print('put_tasks: no more tasks')
    q.put(end_of_queue)

def work(id, q):
    while True:
        task = q.get()

        if task == end_of_queue:
            q.put(task)
            print("DONE")
            return

        print('process %d: %s' % (id, task))
        time.sleep(.1)
    print('process %d: done' % id)

class Sentinel:
    def __init__(self, id):
        self.id = id

    def __eq__(self, other):
        if isinstance(other, Sentinel):
            return self.id == other.id

        return NotImplemented

if __name__ == '__main__':
    q = Queue(2)
    end_of_queue = Sentinel("end of queue")
    task_gen = Process(target=put_tasks, args=(q,))
    processes = [Process(target=work, args=(id, q)) for id in range(0, 3)]
    ...

我似乎无法使用object()作为标记,因为线程似乎可以访问不同的实例,因此它们不能比较相等。

如果您希望生成随机标记,可以使用uuid模块生成随机ID:

import uuid

class Sentinel:
    def __init__(self):
        self.id = uuid.uuid4()

    def __eq__(self, other):
        if isinstance(other, Sentinel):
            return self.id == other.id

        return NotImplemented

最后,zch使用None作为哨兵,只要队列中没有None,这就完全足够了。sentinel方法适用于大多数任意参数。

答案 2 :(得分:0)

我最近查看了同样的问题,并在Python文档中找到了上面的替代答案

看起来“正确”的方法是使用Queue.task_done()方法,即:

def worker():
    while True:
        item = q.get()
        do_work(item)
        q.task_done()

q = Queue()
for i in range(num_worker_threads):
     t = Thread(target=worker)
     t.daemon = True
     t.start()

for item in source():
    q.put(item)

q.join()       # block until all tasks are done