Python(协程)
day32
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
复习yield
1 def f(): 2 print('ok1') 3 count = yield 5 4 print('count:', count) 5 print('ok2') 6 yield 67 7 8 gen = f()#生成器对象 9 10 ret1 = next(gen)#返回5 等同ret1 = gen.send(None) 11 print(ret1) 12 #从count = yield处开始执行 13 ret2 = gen.send(10)#返回67 14 print(ret2)
第十行执行到yield = 5,返回一个5。
下一次从count = yeild开始执行。
ok1 5 count: 10 ok2 67 Process finished with exit code 0
yield协程并发
1 def consumer(name): 2 print("--->starting eating baozi...") 3 while True: 4 new_baozi = yield 5 print("[%s] is eating baozi %s" % (name, new_baozi)) 6 # time.sleep(1) 7 8 def producer(): 9 next(con) 10 next(con2) 11 n = 0 12 while n < 5: 13 n += 1 14 con.send(n) 15 con2.send(n) 16 print("\033[32;1m[producer]\033[0m is making baozi %s" % n) 17 18 if __name__ == '__main__': 19 con = consumer("c1")#创建一个生成器对象, 其中有yield 20 con2 = consumer("c2") 21 p = producer()
可以在19行处设断点调试。
从con,con2中选择con分析程序的执行过程。
next(con)进入到consumer函数,执行到new_baozi = yield阻断,new_baozi = yeild并未执行,且无返回值。
到while循环的con.send(n),使new_baozi = n,并且print,即第9行。
由于是while循环,循环到new_baozi = yield处继续阻断,等下一次for循环的con.send(n)。
producer中send完,consumer便会立即输出,与协程相类似。
我们先给协程一个标准定义,即符合什么条件就能称之为协程:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其它协程
基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的线程,因为它有一点功能没实现,哪一点呢?
执行结果:
--->starting eating baozi... --->starting eating baozi... [c1] is eating baozi 1 [c2] is eating baozi 1 [producer] is making baozi 1 [c1] is eating baozi 2 [c2] is eating baozi 2 [producer] is making baozi 2 [c1] is eating baozi 3 [c2] is eating baozi 3 [producer] is making baozi 3 [c1] is eating baozi 4 [c2] is eating baozi 4 [producer] is making baozi 4 [c1] is eating baozi 5 [c2] is eating baozi 5 [producer] is making baozi 5 Process finished with exit code 0
greenlet
完成不同任务间的切换。
1 # -*- coding:utf-8 -*- 2 from greenlet import greenlet 3 4 def test1(): 5 print(12) 6 gr2.switch() 7 print(34) 8 gr2.switch() 9 10 def test2(): 11 print(56) 12 gr1.switch() 13 print(78) 14 15 gr1 = greenlet(test1) 16 gr2 = greenlet(test2) 17 gr1.switch()#先执行test1函数,遇到gr2.switch(),到test2中,从之前停止处开始执行 18 #完成不同任务间的切换
执行结果:
12
56
34
78
Process finished with exit code 0
gevent
1 import gevent 2 import time 3 4 def func1(): 5 print('\033[31;1m a\033[0m', time.ctime()) 6 gevent.sleep(2)#模拟IO阻塞的情况,阻塞时执行func2中 7 print('\033[31;1m b\033[0m', time.ctime()) 8 9 10 def func2(): 11 print('\033[32;1m c\033[0m', time.ctime()) 12 gevent.sleep(1)#不是time.sleep() 13 print('\033[32;1m d\033[0m', time.ctime()) 14 15 16 gevent.joinall([ 17 gevent.spawn(func1), 18 gevent.spawn(func2), 19 # gevent.spawn(func3), 20 ])
先执行func1,到gevent.sleep(2)时,模拟了IO阻塞,马上执行func2,当func2中的阻塞1秒后,func1还处于阻塞状态,func2继续执行。
执行结果:
a Tue Nov 6 16:37:04 2018 c Tue Nov 6 16:37:04 2018 d Tue Nov 6 16:37:05 2018 b Tue Nov 6 16:37:06 2018 Process finished with exit code 0
协程实战
1 from gevent import monkey 2 monkey.patch_all()#可以检测到阻塞 3 4 import gevent 5 from urllib.request import urlopen 6 import time 7 8 def f(url): 9 print('GET: %s' % url) 10 resp = urlopen(url) 11 data = resp.read() 12 13 with open('xiaohua.html', 'wb') as f: 14 f.write(data) 15 16 print('%d bytes received from %s.' % (len(data), url)) 17 18 # f('http://www.xiaohuar.com/') 19 a = time.time() 20 gevent.joinall([ 21 gevent.spawn(f, 'https://www.python.org/'), 22 gevent.spawn(f, 'https://www.yahoo.com/'), 23 gevent.spawn(f, 'https://github.com/'), 24 ]) 25 26 b = time.time() 27 28 # l = ['https://www.python.org/', 'https://www.yahoo.com/', 'https://github.com/'] 29 # for url in l: 30 # f(url) 31 # c = time.time() 32 33 # print('普通方法所需时间:', c - b)#10s 34 35 print('使用协程所需时间:', b - a)# 2s
爬虫使用协程,爬取网络更快。遇到阻塞会更换任务。
而且monkey可以检测到是否阻塞。
执行结果:
GET: https://www.python.org/ GET: https://www.yahoo.com/ GET: https://github.com/ 65038 bytes received from https://github.com/. 48925 bytes received from https://www.python.org/. 517942 bytes received from https://www.yahoo.com/. 使用协程所需时间: 5.216813802719116 Process finished with exit code 0