协程
1、greenlet+switch
采用greenlet+switch机制来实现协程
greenlet用于创建协程,switch进行协程之间的切换
某个协程在执行的过程中可以随时被其他协程通过switch来打断,转而去执行其他协程,当前的中
现场会被保留,一旦中断的协程再次获得cpu会恢复现场然后从中断处继续执行
这种机制下的协程是串行的,不能并发
举些例子说明一下
采用greenlet+switch机制来实现协程,这种机制下的协程是串行的,不能并发
_ey1.py
from greenlet import greenlet
from time import sleep
def func1():
print("协程1")
def func2():
print("协程2")
def func3():
print("协程3")
if __name__ == '__main__':
# 使用greenlet创建三个协程
g1 = greenlet(func1)
g2 = greenlet(func2)
g3 = greenlet(func3)
print(g1)
# 开启协程
g1.switch() # 将cpu的使用权转移给g1
运行这个程序,开启的是协程1
<greenlet.greenlet object at 0x0000022AA2CD1898>
协程1
在程序尾部g1.switch()后加入一行
g2.switch()
让函数func1运行后睡3秒
def func1():
print("协程1")
sleep(3)
然后运行这个程序,发现打印协程1,过3秒后打印协程2,如果是并发,协程1和协程2应同时打印,说明这种机制下的协程是串行的,不能并发
协程1
协程2
_ey2 .py
from greenlet import greenlet
from time import sleep
def func1():
print("协程1")
sleep(2)
g2.switch() # 在这里协程1发生了中断,转而执行协程2,此时协程1的线程会被cpu保存,一旦协程1再次获得cpu的执行权,就会立即恢复现场然后从端点处继续往下执行
print("协程1恢复执行!")
def func2():
print("协程2")
sleep(4)
g3.switch()
print("协程2恢复执行!")
def func3():
print("协程3")
sleep(8)
g1.switch()
if __name__ == '__main__':
# 使用greenlet创建三个协程
g1 = greenlet(func1)
g2 = greenlet(func2)
g3 = greenlet(func3)
# print(g1)
# 开启协程
g1.switch() # 将cpu的使用权转移给g1
看一下打印的结果
协程1
协程2
协程3
协程1恢复执行!
这里执行的顺序为:
线程1开启 (打印协程1) --> 2秒后开启线程2 (打印协程2) --> 4秒后开启协程3 (打印协程3) --> 8秒后开启协程1,但是是从断点出执行 (打印协程1恢复执行)
2、gevent
_ey3 .py
import gevent
from time import sleep
def func1():
print("协程1")
sleep(2)
def func2():
print("协程2")
sleep(4)
def func3():
print("协程3")
if __name__ == '__main__':
# 用gevent来创建三个协程
g1 = gevent.spawn(func1)
g2 = gevent.spawn(func2)
g3 = gevent.spawn(func3)
# print(g1)
# 将三个协程加入到协程队列中执行
gevent.joinall([g1,g2,g3]) # 这个队列是一个同步对列,三个协程只能串行执行
运行这个程序,先打印协程1,过2秒后打印协程2,过4秒后打印协程3,所以这三个协程也是串行执行
3、gevent + monkey
协程创建出来执行的时候默认是多个协程同步(串行)
使用monkey可以将协程的同步机制转成异步机制
先看一个程序
_ey4 .py
import requests
import gevent
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'}
def func(url,i):
print("协程%d开启"%i)
res = requests.get(url,headers=headers)
html = res.text
print("协程%d执行结束,获取到的响应体的大小为:%d"%(i,len(html)))
if __name__ == '__main__':
urls = [
"https://www.baidu.com/",
"https://www.163.com/",
"https://www.qq.com/",
"https://www.sina.com.cn/",
"https://www.ifeng.com/"
]
# 创建5个协程用于访问5个网站
g_list = []
for i in range(len(urls)):
g = gevent.spawn(func,urls[i],i) # spawn参数分别代表,第一个参数协程的绑定的函数,后面的参数都是函数的参数
g_list.append(g)
# 将5个协程加入到队列中
gevent.joinall(g_list)
查看打印结果,这些都是按顺序执行的,一个协程开启,第二个协程要开启,只有等第一个协程结束后才能开启
协程0开启
协程0执行结束,获取到的响应体的大小为:156399
协程1开启
协程1执行结束,获取到的响应体的大小为:680096
协程2开启
协程2执行结束,获取到的响应体的大小为:228548
协程3开启
协程3执行结束,获取到的响应体的大小为:579635
协程4开启
协程4执行结束,获取到的响应体的大小为:216010
现在,修改一下,使用monkey,这5个线程一起执行,变成了异步
import requests
import gevent
from gevent import monkey
# 用monkey给整个协程队列添加一个非阻塞I/O操作,使其成为一个异步(并发或者并行)的队列
monkey.patch_all()
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'}
def func(url,i):
print("协程%d开启"%i)
res = requests.get(url,headers=headers)
html = res.text
print("协程%d执行结束,获取到的响应体的大小为:%d"%(i,len(html)))
if __name__ == '__main__':
urls = [
"https://www.baidu.com/",
"https://www.163.com/",
"https://www.qq.com/",
"https://www.sina.com.cn/",
"https://www.ifeng.com/"
]
# 创建5个协程用于访问5个网站
g_list = []
for i in range(len(urls)):
g = gevent.spawn(func,urls[i],i) # spawn参数分别代表,第一个参数协程的绑定的函数,后面的参数都是函数的参数
g_list.append(g)
# 将5个协程加入到队列中
gevent.joinall(g_list)