一、前言
Python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病。然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,可以弥补Python性能方面的短板。
-
python3.0时代,标准库里的异步网络模块:select(非常底层)
-
python3.0时代,第三方异步网络库:Tornado
-
python3.4时代,asyncio:支持TCP,子进程.直接内置了对异步IO的支持。
现有的python 异步框架:tornado、fasapi 、django-3.0 asgi、aiohttp... python 中的主流django框架都在往异步靠拢。 不得不说,python要进入异步的时代了,那我也得赶紧学一学啊!
二、什么是协程
协程不是计算机提供的,计算机提供了进程和线程的概念,而协程是我们程序员人为去实现的。也叫微线程,是用户态上下文切换的一种技术。通过一个线程去代码之间游走切换运行。可能有点难以理解,下面就从协程的发展演进中来体会什么是协程。
三、协程的发展
- greenlet 早期模块
- yield 关键字
- asynico 装饰器(py.34)
- asny、await 关键字(py3.5)[官方推荐]
1、greenlet实现协程
# 引入包前需要先安装:pip3 install greenlet
from greenlet import greenlet
def func1():
print(1) # 第2步 输出1
gr2.switch() # 第3步 切换到 func2 函数执行
print(2) # 第6步 输出2
gr2.switch() # 第7步 切换到 func2 函数执行
def func2():
print(3) # 第4步 输出3
gr1.switch() # 第5步 切换到 func1 函数执行
print(4) # 第8步 输出4
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 第1步:去执行func1 函数
结果:
1
3
2
4
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
yeild 如果不了解,可以去先看一下python生成器部分的内容。
3、重头戏 asyncio
必须在python3.4及之后的版本才能用
3.1 asyncio.coroutine 装饰器 (python3.4版本)
import asyncio
@asyncio.coroutine
def func1():
print(1)
# 现在我用的是 sleep(2) 我要是换成 网络请求 意义就比重大了
yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
print(2)
@asyncio.coroutine
def func2():
print(3)
yield from asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# 本来执行程序时应该等待 4 秒,但实际执行时间也就是2秒多一点,没有等待4秒
注意:遇到io阻塞会自动切换。asyncio.sleep(2)就是模拟阻塞两秒的操作。
结果:
1
3
2
4
★3.2 async & await 关键字(Python3.5 及以后的版本才能用)
当前官方推荐的方式
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
四、实例
在一个线程中如果遇到io等待的时间,线程不会一直等待,利用空闲的时间去执行其他的任务 如果那个任务有结果了 在回去拿结果。
下面是不使用异步与使用异步编程对比的案例:
网络io请求获取三张图片
1、不使用异步:
# pip3 install requests
import requests
def download_image(url):
print(f'开始下载图片:{url}')
response = requests.get(url)
print(f'下载完成')
file_name = url.rsplit("/")[-1]
with open(file_name,'wb',) as f:
f.write(response.content)
if __name__ == '__main__':
url_list = [
'http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg',
'http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg',
'http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg',
]
for url in url_list:
download_image(url)
结果:
开始下载图片:http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg
下载完成
开始下载图片:http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg
下载完成
开始下载图片:http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg
下载完成
看结果 是安顺序执行的 如果没哥请求是两分钟的话 那就需要足足等待6分钟。
如果我们用 异步做就会大大的提高性能。
2、使用异步
import asyncio
import aiohttp
async def fetch(session,url):
print(f'发送请求{url}')
async with session.get(url,verify_ssl=False) as response:
content = await response.read()
file_name = url.rsplit('/')[-1]
with open(file_name,'wb') as f:
f.write(content)
print('下载完成')
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg',
'http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg',
'http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg',
]
tasks = [
asyncio.create_task(fetch(session,url)) for url in url_list
]
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
结果:
发送请求http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg
发送请求http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg
发送请求http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg
下载完成
下载完成
下载完成