知乎本身在底层造了很是多的轮子,并且也在服务器部署方面和数据获取方面普遍使用 gevent 来提升并发获取数据的能力。如今开始我将结合实际使用与测试慢慢完善本身对 gevent 更全面的使用和扫盲。git
在对 gevent loop 的使用上,gevent tutorial 介绍得很是敷衍,以致于彻底不知道他的使用办法。这里我将结合 timeit 测试更详细的介绍一下 gevnet.loop 的使用。以及他的父类 Group 的使用。github
其实在使用 gevent 上面我我的一直有一个误区,就是我使用并发的 gevent 必定比我平时线性的操做速度更快,其实不是这样。让咱们来看一个例子:服务器
import timeit
import gevent
def task1(): pass def task(): for i in range(50): pass def async(): x = gevent.spawn(task) x.join() def sync(): for i in range(50): task1() print timeit.timeit(stmt=async, setup=''' from __main__ import task, async, sync ''', number=1000) print '同步开始了' print timeit.timeit(stmt=sync, setup=''' from __main__ import task, async, sync ''', number=1000)
output:
0.0216090679169
同步开始了
0.00430107116699网络
能够看到,咱们一样跑同样的函数调用,若是使用 gevent.spawn 一个调用,咱们会话费更多的资源,这致使了咱们甚至没有线性完成得快。你可能会说,这是固然了,由于这里只 spwan 了一个 gevent 的 greenlet 实例。若是咱们调用多个呢?并发
import timeit import gevent def async1(): p = [] for i in range(50): p.append(gevent.spawn(task1)) gevent.joinall(p) def task1(): pass def sync(): for i in range(50): task1() print timeit.timeit(stmt=async1, setup=''' from __main__ import task, async1, sync ''', number=1000) print '同步开始了' print timeit.timeit(stmt=sync, setup=''' from __main__ import task, async1, sync ''', number=1000) output: 1.21793103218 同步开始了 0.0048680305481
状况彷佛变得更糟糕了。。。。咱们同时 spawn 了 50个 greenlet 实例实图一次性搞定这个事情,可是速度甚至变得更慢了。由此咱们能够得出一个结论,也许在并非在网络请求或者须要等待切换的状况下,使用 gevent 也许不是一个很好的解决方案。app
那到底种状况可使咱们的性能得到巨大的提高?来看这个例子:async
import timeit import gevent def async1(): p = [] for i in range(50): p.append(gevent.spawn(task1)) gevent.joinall(p) def task1(): gevent.sleep(0.001) def sync(): for i in range(50): task1() print timeit.timeit(stmt=async1, setup=''' from __main__ import task1, async1, sync ''', number=100) print '同步开始了' print timeit.timeit(stmt=sync, setup=''' from __main__ import task1, async1, sync ''', number=100) output: 0.25629901886 同步开始了 6.91364789009
能够看出来,此次我 spawn 50个一块儿跑,就远远快于线性了。由于在线性的状况下,咱们每次都会在 task1 任务运行的时候阻塞 0.001s, 可是 gevent 使得 async 函数几乎不受等待影响。很是快速的解决了这个问题。其实这个环境在咱们进行网络 io 的时候很是常见。好比咱们向某个地址下载图片,若是咱们线性下载图片,咱们须要等待第一张图片下载完成以后才能进行第二张图片的下载,可是咱们使用 gevent 并发下载图片,咱们能够先开始下载图片,而后在等待的时候切换到别的任务继续进行下载。当下载完毕以后咱们会切换回来完成下载。不用等待任何一个任务下载完成,大大的提升了效率。函数
gevent 的 pool 函数能够控制并发的时候最多使用 greenlet 的数量。 这里我循环了50次,可是当咱们在进行 io 的时候,咱们设置了 1w 次,那么也会起 10000 个协程来运行这个程序,对于性能咱们是不知道的。有可能会直接堵死服务器端,因此咱们须要对此进行控制,咱们限制最多同时使用 20 个 greenlet 实例进行处理,当有任务完成以后咱们再开始别的任务,更好的控制咱们的请求以及维护至关的效率让咱们来看几个数据:高并发
开 10个 greenlet 的状况oop
import timeit import gevent from gevent.pool import Pool x = Pool(40) def async1(): for i in range(50): x.spawn(task1) x.join() def task1(): gevent.sleep(0.001) def sync(): for i in range(50): task1() print timeit.timeit(stmt=async1, setup=''' from __main__ import task1, async1, sync ''', number=100) print '同步开始了' print timeit.timeit(stmt=sync, setup=''' from __main__ import task1, async1, sync ''', number=100) output:
0.813331842422
同步开始了
6.89506411552
开 40 个实例的状况:
0.366757154465
同步开始了
6.78097295761
开80 个实例的状况:
0.222685098648
同步开始了
6.77246403694
开10000个的状况:
0.227874994278
同步开始了
6.81039714813
能够看到当咱们超过阀值以后,开更多的实例已经没有任何意义了。并且有可能还形成一些性能上的浪费,因此选择一个合适的实例数量便可。
另外还有一个速度更快的函数能够提供使用:
def async1(): for i in range(50): x.imap(task1)
官方文档上还有一句话,就是若是对出的结果并不要求顺序的话可使用imap_unordered,速度更快:
def async1(): for i in range(50): x.imap_unordered(task1)
pool饱和的状况下 上面的例子差很少只要 0.8s 就能处理完,imap 须要1s。使用join须要 0.22s。
Reference:
http://hhkbp2.github.io/gevent-tutorial/#_8 gevent-tutorial