Python已经通过生成器的yield和send方法给我们提供了实现协程的基础设施。
send方法可能比较冷门,send函数可以给一个生成器传递一个值,来启动yield。yield也可以同样作用于send。这样一来一回,就发生了程序的切换,也就是最基本的协程原理。
下面的demo可能更容易懂。
# -*- coding: utf-8 -*-
import time
def consumer():
n = '200 ok'
food = None
while True:
food = yield n
print "eat food %d"%(food)
def produce(c ,size):
for i in range(size):
n = c.send(i)
print "produce food %d and receive msg %s"%(i,n)
def main():
c = consumer() #创建一个生层器
c.next() #启动生成器
produce( c , 10)
if __name__ == '__main__':
main()
但这也太TM糙了吧!这可不符合那些以造轮子来衡量自己水平大牛们的性格,于是就有了gevent这个神级库。
在接下来的阅读前,首先要明白,
1、gevent协程适合I/O密集,不适合CPU密集。
3、gevent协程无法发挥多核优势,事实上,协程只是以单线程的方式在运行。
3、子程序就是协程的一种特例
开胃菜
import gevent
import time
def work():
gevent.sleep(1) #做其他io操作而不是sleep
def asynchronous():
threads = [ gevent.spawn( work) for i in range(5)]
gevent.joinall( threads)
def synchronous():
for i in range(5):
work()
def main():
start = time.time()
asynchronous()
end = time.time()
print ("asynchronous spent %f"%(end-start))
start = time.time()
synchronous()
end = time.time()
print ("asynchronous spent %f"%(end-start))
if __name__ == '__main__':
main()
通常的步骤
thread1 = gevent.spawn(woker)
thread2 = gevent.spawn(woker) #创建路线线程
threads = [thread1, thread2]
gevent.joinall(threads) #进行阻塞
获得Greenlet的状态
一个绿色线程有如下属性
- started :Greenlet是否启动
- ready: Greenlet 是否停止
- value:Greenlet的返回值
- successful: 是否已经停止且没有抛出异常
- exception :获得抛出的异常
避免僵尸进程
gevent.signal(signal.SIGQUIT, gevent.shutdown)
主程序崩溃,就kill掉所有Greenlet。
猴子补丁
给Python标准库做修改来适应gevent,提高性能。
数据结构
事件(event)
类似yield的东西,更好用。用于Greenlet通信。
import gevent
from gevent.event import Event
evt = Event()
def produce():
print('produce....')
gevent.sleep(5)
print('produce one!')
evt.set()
def consume():
print('waiting')
evt.wait()
print('consume one!')
def main():
gevent.joinall([
gevent.spawn(consume),
gevent.spawn(consume),
gevent.spawn(consume),
gevent.spawn(produce)
] )
if __name__ == '__main__':
main()
Event对象不能传值,只能简单通知Greenlet。要传值的话用AsyncResult()对象。
import gevent
from gevent.event import Event,AsyncResult
evt = AsyncResult()
def produce():
print('produce....')
gevent.sleep(5)
print('produce one!')
evt.set("one cake")
def consume():
print('waiting')
something = evt.get()
print('consume %s!'%(something))
def main():
gevent.joinall([
gevent.spawn(consume),
gevent.spawn(consume),
gevent.spawn(consume),
gevent.spawn(produce)
])
if __name__ == '__main__':
main()
队列
Event只能简单的处理消息通知,上面的生产——消费者模型有问题,一个变量被共享了,这不是个好事情。所以有了队列。
import gevent
from gevent.queue import Queue
evt = Queue()
def produce():
print('produce....')
for i in range(1,4):
print('produce one!')
evt.put_nowait(i)
gevent.sleep(i)
def consume():
print('waiting')
something = evt.get()
print('consume %s!'%(something))
def main():
gevent.joinall([
gevent.spawn(consume),
gevent.spawn(consume),
gevent.spawn(consume),
gevent.spawn(produce)
])
if __name__ == '__main__':
main()
get操作可以设置timeout=x,超过时间抛出gevent.queue.Empty。get(timeout=0)==get_nowait
Queue可以设置maxsize。如果队列长度等于maxsize,对于阻塞操作put会阻塞。put_nowait会抛出gevent.queue.Full.
组和池
import gevent
from gevent import getcurrent
from gevent.pool import Group
import time
import random
def work(n):
gevent.sleep(random.randint(1,3))
return n
g = Group()
a=g.imap_unordered(work,range(10))
print 'unorder'
for i in a:
print i,
print
a=g.imap(work,range(10))
print 'order'
for i in a:
print i,
在学习group的时候让我蒙逼的是map和imap并没有指定大小组大小,后来看了源码,发现,map其实是imap的包装,唯一区别是imap返回迭代对象,map返回的是 list(map)。造成一个很大的区别是,imap是lazy的,也就是你不用它的计算结果,imap就不会去计算,而map要获得一个imap的list结果,于是是立即执行的。!
imap可以设置maxsize, map不行,所以map调用的是imap的根据情况设置的大小。
在计算过程中,imap 有有序和无序两种计算方式。 具体看上面的demo。无序的更轻量级,效率好一点。
那group和pool有什么区别呢?func
在我看来,其实没有本质区别!看源码的注释
A pool is like a group, but the maximum number of members is governed by the size parameter.
通过源码,发现pool其实是group的子类,唯一的差别是pool限制了Greenlets的数量。所以用法也差不多。
偷个懒,官网上写的demo,
from gevent.pool import Pool
class SocketPool(object):
def __init__(self):
self.pool = Pool(1000)
self.pool.start()
def listen(self, socket):
while True:
socket.recv()
def add_handler(self, socket):
if self.pool.full():
raise Exception("At maximum pool size")
else:
self.pool.spawn(self.listen, socket)
def shutdown(self):
self.pool.kill()
锁和信号量
这是老生长谈的话题了,并行编程总会遇到竞争条件问题。毫无疑问,gevent提供了信号量。
from gevent.coros import BoundedSemaphore
sem = BoundedSemaphore(2)
BoundedSemaphore的参数表示允许多少个greenlet进入临界区,只有一个的时候就成为
了锁。
使用方法:
sem.acquire()
#condition race
sem.release()
或者担心忘记释放信号量这种低级错误,可以用with上下文管理器。
with sem:
#condition race