概述:
为什么要学异步非阻塞和asyncio?
异步非阻塞是实现高并发的一种很好的技术手段,除此之外异步框架越来越多,如tonado、fastapi、django 3.x asgi、aiohttp都发展,来提高性能。
内容?
什么是协程
asyncio模块进行异步编程
实战案例
一、协程:
协程不是计算机提供的,是程序员人为创造的。
协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是用过一个线程实现代码相互切换执行,例如:
(一)非协程的情况,代码在一个线程中由上至下依次执行
deffunc1():print(1)
...print(2)deffunc2():print(3)
...print(4)
func1()
func2()
(二)实现协程的方式:
greenlet,早期模块
yield关键字
asyncio模块(py3.4后)
async、await关键字(py3.5后,推荐使用)
(三)
1.1 greenlet
1.2 yield关键字
1.3 asycio
在python3.4及之后版本才能用
importasyncio
@asyncio.coroutinedeffunc1():print(1)#网络IO请求:下载一张图片
yield from asynco.sleep(2) #遇到IO耗时操作,自动化切换到task中的其他任务
print(2)
@asyncio.coroutinedeffunc2():print(3)#网络IO请求:下载一张图片
yield from asynco.sleep(2) #遇到IO耗时操作,自动化切换到task中的其他任务print(4)
task=[
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2()),
]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
注意:遇到IO阻塞自动切换。不加协程前原来是一张张下,需要4秒,现在遇到IO切换只需要2秒
1.4 asyc & await 关键字
在python3.5及之后的版本引用了这两个关键字,本质上是为了方便引用把@asyncio.coroutine==>async,yield from==>await。
importasyncio
asyncdeffunc1():print(1)#网络IO请求:下载一张图片
await asynco.sleep(2) #遇到IO耗时操作,自动化切换到task中的其他任务
print(2)
asyncdeffunc2():print(3)#网络IO请求:下载一张图片
await asynco.sleep(2) #遇到IO耗时操作,自动化切换到task中的其他任务
print(4)
task=[
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2()),
]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
综上:1.1的greenlet是asyncio出来之前实现的协程方式,1.2和1.3都是方便说明1.4的例子,在现实中用greenlet和asyc & await 关键字实现协程较多。
二、协程的意义
在一个线程中如果遇到IO等待时间,线程不会傻傻等,而是利用空闲的时候再去干点其他的事。
案例:下载三张图片(网络IO)
(一)普通方式(同步)
"""pip3 install requests"""
importrequestsdefdownlaod_image(ulr):print("开始下载:",url)#发送网络请求,下载图片
response =requests.get(url)#图片保存到本地文件
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://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1819216937,2118754409&fm=26&gp=0.jpg',]for item inurl_list:
download_image(item)
(二)协程方式(异步)
"""下载图片使用第三发模块aiohttp,提前安装:pip3 install aiohttp"""
importaiohttpimportasyncio
asycncdeffetch(session,url):print("发送请求:",url)
async with session.get(url,verify_ssl-False) as response:
content=await response.content.read()
file_name= url.rsplit('_')[-1]
async def main():
async with aiohttp.Clientsession() as session:
url_list = [
'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1819216937,2118754409&fm=26&gp=0.jpg',
]
task = [asyncio.create_task(fetch(session,url)) for url in url_list]
await asyncio.await(tasks)
if __name__ =='__main__':
asyncio.run(main())
同步和异步区别:
同步是一个任务执行完才能执行下一个,异步编程是,不等上一个任务完成完,接着进行下一个任务。(二)就是基于协程的方式实现异步编程。当然通过其他的进程池和线程池也同样可以实现异步编程。
三、异步编程
(一)事件循环
理解成为一个死循环,去检测并执行某些代码
#伪代码
任务列表=[任务1,任务2,任务3,。。。]whileTrue:
可执行的任务列表,已完成的任务列表=去任务列表中检查所有的任务,将’可执行‘和’已完成的‘的任务返回for 就绪任务 in可执行的任务列表:
执行已就绪的任务for 已完成的任务 in已完成的任务列表:
在任务列表中的移除 已完成的任务
如果 任务列表 中的任务都已完成,则终止循环
importasyncio#去生成或获取一个事件循环
loop =asyncio.get_event_loop()#将任务放到任务列表中
loop.run_until_complete(任务)
(二)快速上手
协程函数,定义函数时格式:async def 函数名
协程对象,执行协程函数(),得到的是协程对象
ascync deffunc():passresult= func()
注意:执行协程函数,创建的是协程对象,但是函数内部代码不会执行
如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理。
importasyncio
asyncdeffunc():print(”快来执行“)
result=func()#python3.7之前的执行方法#loop = asyncio.get_event_loop()#loop.run_until_complete(result)
#python3.7后的执行方法,本质上是将上述两个函数进行了封装
asyncio.run(result)
(三)await
await+可等待对象(如协程对象、Future、Task对象,这些都是IO等待对象)
示例1:
importasyncio
asyncdeffunc():print("来玩呀")#函数执行到这里,因为是await+可等待对象,会跳出该任务,把资源空出来,等response = await asyncio.sleep(2)在继续执行下面的任务
response = await asyncio.sleep(2)print("结束",response)
asyncio.run( func() )
示例2:
importasyncio
asyncdefothers():print("start")
await asyncio.sleep(2)print('end')return "返回值"
asyncdeffunc():print("执行协程函数内部代码")#遇到IO操作挂起当前协程(任务),等待IO操作完成之后在继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
response =await others()print("IO请求结束,结果为:",response)
asyncio.run( func() )
执行结果:
执行协程函数内部代码
start
end
IO请求结束,结果为: 返回值
代码执行顺序为:
func() —>print("执行协程函数内部代码")—>response = await others(),判断该对象为IO操作,挂起当前协程,然后执行others()—>print("start")—>await asyncio.sleep(2)
——>2秒后回来执行print('end')——>return '返回值’,把'返回值’返回给response。
response = await others()这里我理解await的作用是,判断后面的时候是可等待对象,如果是话就执行yield from others(),挂起当前协程,执行others()函数。
示例3:一个协程任务中有多个await
import async
async def others():
print("start")
await asyncio.sleep(2)
print('end')
return "返回值"
async def func():
print("执行协程函数内部代码")
#遇到IO操作挂起当前协程(任务),等待IO操作完成之后在继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
response1 = await others()
print("IO请求结束,结果为:",response1)
response2 = await others()
print("IO请求结束,结果为:",response2)
asyncio.run( func() )
执行结果为:
执行协程函数内部代码
start
end
IO请求结束,结果为: 返回值
start
end
IO请求结束,结果为: 返回值
(四)Task对象
Tasks are used to schedule coroutines concurrently.
When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon:
白话:在事件循环中添加多个任务的。
Task用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行,除了使用asyncio
高并发是一种场景描述,异步是一种技术手段。
并发强调的是N人干同样的事,要保证不争抢 (lock,atomic,synchronize,volatile, cas)
异步强调的是1/N人干不同的事,不该等的别等 (thread pool, future, async,reactive)