协程简介
区别:线程和进程的操作是由程序触发系统接口,执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。
协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协成的原理:利用一个线程,分解一个线程成为多个微线程,注意此时从程序级别来分解的
适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
实现技术
需要手动安装 greenlet模块 和 gevent模块。安装gevent模块会默认安装greenlet模块
基于底层greenlet+switch实现
采用greenlet+switch机制来处理协程。greenlet用于创建协程,switch进行协程之间的切换。
某个协程在执行的过程中可以被其他协程通过switch中断转而去执行其他协程,
当前中断的协程的现场会被保存,一旦中断的协程再次获得cpu以后首先会恢复现场继续执行。
这种机制下的协程之间是串行执行,没有办法实现并发
1 from greenlet import greenlet 2 3 def test1(): 4 print(1,2) # 2.执行输出语句,输出1,2 5 gr2.switch() # 3.执行完上面输出语句后,此行调用test2函数 6 print(3,4) # 6.执行输出语句,输出3,4 7 gr2.switch() # 7.执行完上面输出语句后,此处调用test2函数 8 9 def test2(): 10 print(5,6) # 4.执行输出语句,输出5,6 11 gr1.switch() # 5.执行完上面输出语句后,此行调用test1函数 12 print(7,8) # 8.执行输出语句,输出7,8 13 14 gr1 = greenlet(test1) # 1.调用grl1对应的test1函数 15 gr2 = greenlet(test2) 16 gr1.switch()
1 1,2 2 5,6 3 3,4 4 7,8
基于底层gevent+sleep实现
gevent+sleep机制通过对协程进行阻塞来实现协程之间切换与同步
1 import gevent 2 3 def foo(): 4 print('Running in foo') 5 gevent.sleep(0) 6 print('Explicit context switch to foo again') 7 8 def bar(): 9 print('Explicit context to bar') 10 gevent.sleep(0) 11 print('Implicit context switch back to bar') 12 13 gevent.joinall([ 14 gevent.spawn(foo), 15 gevent.spawn(bar), 16 ])
1 Running in foo 2 Explicit context to bar 3 Explicit context switch to foo again 4 Implicit context switch back to bar
基于底层gevent+monket实现
1 import gevent 2 from gevent import monkey 3 import requests 4 5 # 用monkey给整个协程程序添加一个非阻塞I/O的操作 6 monkey.patch_all() 7 headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"} 8 9 def fn(url,i): 10 print("协程%d开启!"%i) 11 res = requests.get(url=url,headers=headers) 12 html = res.text 13 print("协程%d执行结束,响应体的大小为:%d"%(i,len(html))) 14 15 if __name__ == '__main__': 16 urls = [ 17 "https://www.baidu.com/", 18 "https://www.163.com/", 19 "https://www.qq.com/", 20 "https://www.sina.com.cn/", 21 "https://www.ifeng.com/" 22 ] 23 # 创建一个列表用于管理所有的协程 24 g_list = [] 25 for i in range(len(urls)): 26 # 创建协程 27 g = gevent.spawn(fn,urls[i],i) 28 g_list.append(g) 29 # 启动所有的协程 30 gevent.joinall(g_list)
1 协程1开启! 2 协程2开启! 3 协程3开启! 4 协程4开启! 5 协程1执行结束,响应体的大小为:673160 6 协程0执行结束,响应体的大小为:156755 7 协程4执行结束,响应体的大小为:220961 8 协程3执行结束,响应体的大小为:564692 9 协程2执行结束,响应体的大小为:231551
实例
1 from gevent import monkey; monkey.patch_all() 2 import gevent 3 import requests 4 5 def f(url): 6 print('GET: %s' % url) 7 resp = requests.get(url) 8 data = resp.text 9 print('%d bytes received from %s.' % (len(data), url)) 10 11 gevent.joinall([ 12 gevent.spawn(f, 'https://www.python.org/'), 13 gevent.spawn(f, 'https://www.yahoo.com/'), 14 gevent.spawn(f, 'https://github.com/'), 15 ])
1 GET: https://www.python.org/ 2 GET: https://www.yahoo.com/ 3 GET: https://github.com/ 4 431218 bytes received from https://www.yahoo.com/. 5 25529 bytes received from https://github.com/. 6 47394 bytes received from https://www.python.org/.