1 协程 Coroutine
协程不是计算机提供的,是程序员认为创造的。也可以称为微线程,是一种用户态的上下文切换技术。
简而言之,其实就是通过一个线程实现代码块相互切换执行。
def func1():print(1)...print(2)def func2():print(3)...print(4)func1()func2()1234
上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4。但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4 。
在python中有很多种方法用来实现协程:
greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现)yeild生成器,借助生成器的特点也可以实现协程代码asyncio,在python3.4中引入该装饰器编写协程代码async**await在python3.5中引入的两个关键字,结合asyncio模块可以更方便编写协程代码。****
1.1 greenlet实现协程
greentlet是一个第三方模块,需要提前安装 pip3 install greenlet才能使用。
import greenlet
def func1(num):
print(num)
num+=1 # 第1步:输出 1
gr2.switch(num) # 第3步:切换到 func2 函数
print(num) # 第6步:输出 2
num+=1
gr2.switch(num) # 第7步:切换到 func2 函数,从上一次执行的位置继续向后执行
def func2(num):
print(num)
num+=1 # 第4步:输出 3
gr1.switch(num) # 第5步:切换到 func1 函数,从上一次执行的位置继续向后执行
print(num) # 第8步:输出 4
gr1 = greenlet.greenlet(func1)
gr2 = greenlet.greenlet(func2)
gr1.switch(1) # 第1步:去执行 func1 函数
1
2
2
3
注意:switch中也可以传递参数用于在切换执行时相互传递值。
1.2 yield 关键字
def func1():
yield 1
yield from func2()
yield 2
def func2():
yield 3
yield 4
f1 = func1()
for item in f1:
print(item)
1
3
4
2
1.3 asyncio
在Python3.4之前官方未提供协程的类库,一般大家都是使用greenlet等其他来实现。在Python3.4发布后官方正式支持协程,即:asyncio模块。
Python3.8之后 [@asyncio](https://github.com/asyncio).coroutine 装饰器就会被移除,推荐使用 async & awit 关键字实现协程代码
import asyncio
@asyncio.corountine
def func1():
print(1)
# 遇到IO耗时操作,自动切换到task中其中的其他任务
# 比如 网络IO请求:下载一张图片
yield from asyncio.sleep(2)
print(2)
@asyncio.coroutine
def func2():
print(3)
# 网络IO请求:下载一张图片
yield from asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_furture(func1()),
asyncio.ensure_furture(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(task))
DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
def fun2():
1
3
2
4
遇到IO阻塞自动切换
1.4 async & await关键字
async & awit 关键字在Python3.5版本中正式引入,基于他编写的协程代码其实就是 上一示例 的加强版,让代码可以更加简便。
import asyncio
aynsc def func1():
print(1)
await asycio.sleep(2)
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
task = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(task))
2 协程的意义
协程可以通过一个线程在多个上下文进行来回切换执行。
- 计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态,反倒会降低性能
- IO型的操作,利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后就自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码)
2.1 爬虫案例
方法一:同步爬虫
方式一:同步编程实现
"""
下载图片使用第三方模块requests,请提前安装:pip3 install requests
"""
import requests
def download_image(url):
print("开始下载:",url)
# 发送网络请求,下载图片
response = requests.get(url)
print("下载完成")
# 图片保存到本地文件
file_name = url.rsplit('_')[-1]
with open(file_name, mode='wb') as file_object:
file_object.write(response.content)
if __name__ == '__main__':
url_list = [
'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
]
for item in url_list:
download_image(item)
方法二:异步爬虫
import asyncio
import requests
import aiohttp
async def fetch(session, url):
print('send request...', url)
# 发送网络请求下载
async with session.get(url, verity_ssl=False) as response:
content = await response.content.read()
file_name = url.rsplit('_')[-1]
with open(fiel_name, mode='wb') as f:
f.write(content)
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
]
tasks = [
asyncio.create_task(fetch(session,url)) for url in url_list
]
await asyncio.wait(tasks)
if __name__ == "__main__":
asyncio.run(main())
