本文首发于:行者AI
在近期的编码工作过程中遇到了async和await装璜的函数,查问材料后理解到这种函数是基于协程的异步函数。这类编程形式称为异步编程,罕用在IO较频繁的零碎中,如:Tornado web框架、文件下载、网络爬虫等利用。协程可能在IO等待时间就去切换执行其余工作,当IO操作完结后再主动回调,那么就会大大节俭资源并提供性能。接下来便简略的解说一下异步编程相干概念以及案例演示。
1. 协程简介
1.1 协程的含意及实现办法
协程(Coroutine),也能够被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块互相切换执行。例如:
def func1(): print(1) ... # 协程染指 print(2) def func2(): print(3) ... # 协程染指 print(4)func1()func2()
上述代码是一般的函数定义和执行,按流程别离执行两个函数中的代码,并先后会输入:1、2、3、4
。但如果染指协程技术那么就能够实现函数见代码切换执行,最终输出:1、3、2、4
。
在Python中有多种形式能够实现协程,例如:
- greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现);
- yield,生成器,借助生成器的特点也能够实现协程代码;
- asyncio,在Python3.4中引入的模块用于编写协程代码;
- async & awiat,在Python3.5中引入的两个关键字,联合asyncio模块能够更不便的编写协程代码。
前两种实现形式较为老旧,所以重点关注前面的形式
规范库实现办法
asyncio是Python 3.4版本引入的规范库,间接内置了对异步IO的反对。
import asyncio@asyncio.coroutinedef func1(): print(1) yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其余工作 print(2)@asyncio.coroutinedef func2(): print(3) yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其余工作 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
关键字在Python3.5版本中正式引入,代替了asyncio.coroutine
装璜器,基于他编写的协程代码其实就是上一示例的加强版,让代码能够更加简便可读。
import asyncioasync 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))
1.2 案例演示
例如:用代码实现下载 url_list
中的图片。
- 形式一:同步编程实现
# requests库仅反对同步的http网络申请import requestsdef download_image(url): 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)print("下载实现")if __name__ == '__main__': url_list = [ 'https://www.1.jpg', 'https://www.2.jpg', 'https://www.3.jpg' ] for item in url_list: download_image(item)
输入:按程序发送申请,申请一次下载一张图片,如果每次下载破费1s,实现工作须要3s 以上。
- 形式二:基于协程的程实现
# aiohttp 为反对异步编程的http申请库import aiohttpimport asyncioasync def fetch(session, url): print("发送申请:", url) async with session.get(url, verify_ssl=False) as response: content = await response.content.read() file_name = url.rsplit('_')[-1] with open(file_name, mode='wb') as file_object: file_object.write(content)async def main(): async with aiohttp.ClientSession() as session: url_list = [ 'https://www.1.jpg', 'https://www.2.jpg', 'https://www.3.jpg' ] tasks = [asyncio.create_task(fetch(session, url)) for url in url_list] await asyncio.wait(tasks)if __name__ == '__main__': asyncio.run(main())
输入:一次发送三个下载申请,同时下载,如果每次下载破费1s,实现工作仅须要1s 左右,第一种办法的耗时为第二种的三倍。
1.3 小结
协程能够让原来要应用异步+回调形式写的非人类代码,用看似同步的形式写进去。
2. 异步编程简介
2.1 同步和异步的区别
同步 :循序渐进执行操作、申请
异步:无需期待上一步操作、申请实现,就开始下一步(每个操作依然有先后顺序)
目前python异步相干的支流技术是通过蕴含关键字async&await的async模块实现。
2.2 异步编程-事件循环
事件循环,能够把他当做是一个while循环,这个while循环在周期性的运行并执行一些工作,在特定条件下终止循环。
# 伪代码工作列表 = [ 工作1, 工作2, 工作3,... ]while True: 可执行的工作列表,已实现的工作列表 = 去工作列表中查看所有的工作,将'可执行'和'已实现'的工作返回 for 就绪工作 in 已准备就绪的工作列表: 执行已就绪的工作 for 已实现的工作 in 已实现的工作列表: 在工作列表中移除 已实现的任 如果 工作列表 中的工作都已实现,则终止循环
在编写程序时候能够通过如下代码来获取和创立事件循环。
# 形式一:import asyncio# 生成或获取一个事件循环loop = asyncio.get_event_loop()# 将工作增加到事件循环中loop.run_until_complete(工作)# 形式二(python3.7及以上版本反对):asyncio.run( 工作 )
2.3 异步编程-疾速上手
async 关键字
- 协程函数:定义函数时候由async关键字装璜的函数
async def 函数名
- 协程对象:执行协程函数()失去的协程对象。
# 协程函数async def func(): pass# 协程对象result = func()
留神:执行协程函数只会创立协程对象,函数外部代码不会执行。如果想要运行协程函数外部代码,必须要将协程对象交给事件循环来解决。
import asyncio async def func(): print("执行协程函数外部代码!")result = func()# 调用办法1:# loop = asyncio.get_event_loop()# loop.run_until_complete( result )# 调用办法2:asyncio.run( result )
await 关键字
await + 可期待的对象(协程对象、Future、Task对象 -> IO期待),遇到IO操作挂起以后协程(工作),等IO操作实现之后再持续往下执行。以后协程挂起时,事件循环能够去执行其余协程(工作)。
import asyncioasync def others(): print("start") await asyncio.sleep(2) print('end') return '返回值'async def func(): print("执行协程函数外部代码") # await期待对象的值得到后果之后再持续向下走 response = await others() print("IO申请完结,后果为:", response)asyncio.run( func() )
Task 对象
Task对象的作用是在事件循环中增加多个工作,用于并发调度协程,通过asyncio.create_task(协程对象)
的形式创立Task对象,这样能够让协程退出事件循环中期待被调度执行。
async def module_a(): print("start module_a") await asyncio.sleep(2) # 模仿 module_a 的io操作 print('end module_a') return 'module_a 实现'async def module_b(): print("start module_b") await asyncio.sleep(1) # 模仿 module_a 的io操作 print('end module_b') return 'module_b 实现' task_list = [ module_a(), module_b(), ]done,pending = asyncio.run( asyncio.wait(task_list) )print(done)
2.4 案例演示
例如:用代码实现连贯并查询数据库的同时,下载一个APK文件到本地。
import asyncioimport aiomysqlimport osimport aiofiles as aiofilesfrom aiohttp import ClientSessionasync def get_app(): url = "http://www.123.apk" async with ClientSession() as session: # 网络IO申请,获取响应 async with session.get(url)as res: if res.status == 200: print("下载胜利", res) # 磁盘IO申请,读取响应数据 apk = await res.content.read() async with aiofiles.open("demo2.apk", "wb") as f: # 磁盘IO申请,数据写入本地磁盘 await f.write(apk) else: print("下载失败")async def excute_sql(sql): # 网络IO操作:连贯MySQL conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123', db='mysql', ) # 网络IO操作:创立CURSOR cur = await conn.cursor() # 网络IO操作:执行SQL await cur.execute(sql) # 网络IO操作:获取SQL后果 result = await cur.fetchall() print(result) # 网络IO操作:敞开链接 await cur.close() conn.close()task_list = [get_app(), execute_sql(sql="SELECT Host,User FROM user")]asyncio.run(asyncio.wait(task_list))
代码逻辑剖析:
【step1】asyncio.run()
创立了事件循环。wait()
办法将task工作列表退出到以后的事件循环中;(留神:必须先创立事件循环,后退出工作列表,否则会报错)
【step2】事件循环监听事件状态,开始执行代码,先执行列表中的get_app()
办法,当代码执行到async with session.get(url)as res:
时,遇到await关键字示意有IO耗时操作,线程会将该工作挂起在后盾执行,并切换到另外一个异步函数excute_sql()
;
【step3】当代码执行到excute_sql()
的第一个IO耗时操作后,线程会反复先前的操作,将该工作挂起,去执行其余可执行代码。如果此时事件循环监听到get_app()
中的第一IO耗时操作曾经执行实现,那么线程会切换到该办法第一个IO操作后的代码,并按程序执行直到遇上下一个await装璜的IO操作;如果事件循环监听到excute_sql()
中的第一个IO操作先于get_app()
的第一个IO操作实现,那么线程会继续执行excute_sql
的后续代码;
【step4】线程会反复进行上述第3点中的步骤,直到代码全副执行实现,事件循环也会随之进行。
2.5 大节
一般来说CPU的耗时运算形式有:
计算密集型的操作:计算密集型工作的特点是要进行大量的计算、逻辑判断,耗费CPU资源,比方计算圆周率、对视频进行高清解码等等。
IO密集型的操作:波及到网络、磁盘IO的工作都是IO密集型工作,这类工作的特点是CPU耗费很少,工作的大部分工夫都在期待IO操作实现(因为IO的速度远远低于CPU和内存的速度)。
异步编程基于协程实现,如果利用协程实现计算密集型操作,因为线程在上下文之间的来回切换总会经验相似于”计算“-->”保留“-->”创立新环境“ 的一系列操作,导致系统的整体性能反而会降落。所以异步编程并不适用于计算密集型的程序。然而在IO密集型操作汇总,协程在IO等待时间就去切换执行其余工作,当IO操作完结后再主动回调,那么就会大大节俭资源并提供性能。