Python异步编程
前言
现在是 Python3.5 以后已经进入异步时代
Python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病。然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,弥补了Python性能方面的短板.
-
python3.0时代,标准库里的异步网络模块:select(非常底层)
-
python3.0时代,第三方异步网络库:Tornado
-
python3.4时代,asyncio:支持TCP,子进程.直接内置了对异步IO的支持。
现有的python 异步框架
- tornado、fasapi 、djanao-3.0 asgi、aiohttp 等等等。。。
python 中的主流django框架都在 往异步靠拢 不得不说 要进入异步的时代了 ,而我还不会异步编程那就可悲了。
核心重点 异步编程 来提高性能。
主要以 这以下三点来聊:
- 协程
- asyncio模块进行异步编程
- 实战案例
协程
协程不是计算机提供的,计算机提供了进程和线程的概念,而协程是我们程序员人为。也叫微线程,用户态上下文切换的一种技术。
通过一个线程去代码之间游走切换之间去运行。
例如:
普通的执行
def func1(): print(1) print(2) def func(2): print(3) print(4) func1() func2()
结果:
1
2
3
4
实现协程
- greenlet ,早期模块
- yield 关键字
- asynico 装饰器(py.34)
- asny、await 关键字(py3.5)[官方推荐]
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
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 2 4
yeild 这个实现的 了解即可
重头戏 asyncio
必须在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 秒 执行时间其实也就是1秒 没有等待4秒
注意⚠️:遇到io阻塞自动切换
结果
1 3 2 4
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))
那我们最常用的就是 async & await 关键字
协程的意义
在一个线程中如果遇到io等待的时间,线程不会一直等待,利用空闲的时间去执行其他的任务 如果那个任务有结果了 在回去拿结果。
案例:
网络io请求获取三张图片
普通方式(同步编程)
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分钟。
如果我们用 异步做就会大大的提高性能。
协程方式(异步编程)
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 下载完成 下载完成 下载完成