Python Gevent共享队列(侦听器进程)

时间:2015-11-04 20:50:18

标签: python logging queue gevent

我试图让一些代码工作,我可以使用gevent实现登录到多线程程序。我想要做的是设置自定义日志处理程序以将日志事件放入队列,而侦听器进程持续监视新日志事件以便适当处理。我以前使用Multiprocessing完成了这项工作,但从未使用过Gevent。

我遇到的问题是程序陷入无限循环(监听程序进程),而不允许其他线程进行工作" ...

理想情况下,在工作进程完成后,我可以将任意值传递给侦听器进程,告诉它打破循环,然后将所有进程连接在一起。这就是我到目前为止所拥有的:

import gevent
from gevent.pool import Pool
import Queue
import random
import time

def listener(q):
    while True:
        if not q.empty():
            num = q.get()
            print "The number is: %s" % num
            if num <= 100:
                print q.get()
            # got passed 101, break out
            else:
                break
        else:
            continue
def worker(pid,q):
    if pid == 0:
        listener(q)
    else:
        gevent.sleep(random.randint(0,2)*0.001)
        num = random.randint(1,100)
        q.put(num)

def main():
    q = Queue.Queue()
    all_threads = []
    all_threads = [gevent.spawn(worker, pid,q) for pid in xrange(10)]
    gevent.wait(all_threads[1:])
    q.put(101)
    gevent.joinall(all_threads)

if __name__ == '__main__':
    main()

正如我所说,该程序似乎在第一个过程中被挂起,并且不允许其他工作人员做他们的事情。我也尝试完全单独生成监听程序进程(这实际上我更愿意这样做),但这似乎没有用,所以我尝试了这种方式。

任何帮助都会受到赞赏,感觉我可能只是错过了关于gevent后端的明显信息。

由于

1 个答案:

答案 0 :(得分:1)

第一个问题是如果队列最初为空,那么你的监听器永远不会产生。你产生的第一个任务是你的听众。当它开始时,有while True:,q将为空,所以你去了else分支,它只是继续,循环回到while循环的开始,然后q仍然是空的。所以你只是坐在第一个线程中不断检查q是空的。

这里的关键是gevent不使用&#34; native&#34;线程或进程。不像&#34;真实&#34;线程,可以在任何时候通过幕后的东西(如你的操作系统调度程序)切换到,gevent使用&greenlets&#39;这需要你做一些事情来产生控制&#34;另一项任务。这是gevent认为会阻止的东西,例如从网络,磁盘读取或使用其中一个阻塞gevent操作。

一个原始修复是在pid == 9而不是0时启动你的监听器。通过使它最后生成,q中将有项目,它将进入主if分支。缺点是这并没有解决逻辑问题,所以第一次排队时,你会再次陷入无限循环。

更正确的解决方法是放置gevent.sleep()而不是continue。 sleep是一个阻塞操作,所以你的其他任务将有机会运行。没有参数,它等待没有时间,但仍然让gevent有机会决定切换到另一个任务,如果它准备好运行。但是,这仍然不是非常有效,就好像队列是空的一样,它将花费大量无意义的时间一遍又一遍地检查,并要求尽快再次运行。睡眠时间超过默认值0将更有效,但会延迟处理您的日志消息。

但是,您可以利用以下事实:许多gevent类型(如Queue)可以以更多Pythonic方式使用,并使您的代码更简单,更易于理解,以及更多高效。

import gevent
from gevent.queue import Queue

def listener(q):
    for msg in q:
        print "the number is %d" % msg

def worker(pid,q):
    gevent.sleep(random.randint(0,2)*0.001)
    num = random.randint(1,100)
    q.put(num)

def main():
    q = Queue()
    listener_task = gevent.spawn(listener, q)
    worker_tasks = [gevent.spawn(worker, pid, q) for pid in xrange(1, 10)]
    gevent.wait(worker_tasks)
    q.put(StopIteration)
    gevent.join(listener_task)

这里,Queue可以作为for循环中的迭代器运行。只要有消息,就会获得一个项目,运行循环,然后等待另一个项目。如果没有物品,它将会阻挡并徘徊,直到下一个物品到达。但是,由于它会阻塞,gevent会切换到你要运行的其他任务之一,避免了你的示例代码所带来的无限循环问题。

因为这个版本使用Queue作为for循环迭代器,所以我们也可以自动将一个很好的sentinel值放入队列中以使侦听器任务退出。如果for循环从其迭代器获得StopIteration,它将完全退出。因此,当我从q读取的for循环从q获得StopIteration时,它退出,然后函数退出,并且生成的任务完成。