快速带你搞懂python协程 (async await asyncio)

发布网友 发布时间:4小时前

我来回答

1个回答

热心网友 时间:1分钟前

1 为什么需要协程?

为什么现在越来越多的语言都开始支持协程?

一般来说, 一个线程栈大小为1MB, 如果都用多线程, 那么在高并发下, cpu大部分的时间都将用于切换线程上下文, 而且线程的切换是在内核态完成的, 会耗费额外的空间和时间.而且由于内存都分配给线程栈了, 将频繁地进行内存置换算法, 浪费了很多cpu时间片.

协程, 可以理解为一种在线程里跑的子线程, 它的默认栈空间很小 (比如go的协程栈默认大小为2KB). 当多个协程在一个线程上运行时, 协程间会切换着运行, 协程的切换完全在用户态完成, 而且时机由程序员来自行调度, 从而使得线程的并发量大大提升。

不过协程只适用于IO密集型程序(大部分时间在等待), 对于计算密集型程序, 协程的优势并不大, 因为没有给它切换的时间, cpu大部分时间都在工作。

2 如何定义异步函数?#普通函数定义defadd1(x):print(x+1)returnx+1#异步函数的定义asyncdefadd2(x):print("inasyncfunadd")returnx+2

async关键字定义的函数就是异步函数, 异步函数的实例化对象就是一个future

#add2(1)就是一个futurefuture=add2(1)#一个future对象就是一个协程

注意: 我这里说的是 异步函数的实例化对象 就是一个协程, 你可能会理解为异步函数的调用, 但我认为不合理, 因为这个协程并不会因为这个"调用"而开始执行. 在实例化后,这个协程的状态是pending, 即将要发生的

3 如何切换异步函数?

await后面必须跟一个协程(future), 就可以阻塞当前协程, 切换到这个新协程里执行

你可以把await认为是启动协程的一种方式, 和普通函数调用的效果相同

importasyncio

async def fn2(): print("fn2")

async def fn1(): print("start fn1") await fn2() print("end fn1")

async def main(): print("start main") await fn2() await fn1() print("end main")

if name == 'main': loop = asyncio.get_event_loop() # 这个线程创建一个事件循环 loop.run_until_complete(main()) # 运行异步函数直到完成

执行结果为:

start main fn2 start fn1 fn2 end fn1 end main

#4如何在一个线程内并发执行多个异步函数?##(1)创建事件循环一个普通的线程要能同时处理多个异步函数,就要创建一个事件循环:```pythonimportasynciodefmain():loop=asyncio.new_event_loop()

注: python3.7及以后不再使用事件循环的写法, 而是使用asyncio.run(), 但本质上是一样的, 只是它把事件循环封装在内部了, 个人还是比较喜欢用asyncio.new_event_loop(), 因为它代表的是协程的本质-事件循环

(2) 事件循环的机制

在事件循环中, 会执行所有任务(即异步函数)

但同一时间, 只有一个任务在执行

当一个任务中执行await后, 此任务被挂起, 事件循环执行下一个任务

(3) 代码实战

比如, 现实生活中的一个例子, 点完外卖, 之后玩游戏, 等着外卖送到, 如何用协程实现这样一个案例呢?

这里可能有人会问, 为什么要用asyncio.sleep, 而不用time.sleep呢? 因为, await后面一个要跟一个future(一个异步函数的实例化对象), 可是time.sleep并不是异步函数, 也就不支持协程间切换, 就没法实现并发, 只能串行

importasyncioimporttime

async def play_game(): """玩游戏""" print('start play_game') await asyncio.sleep(1) print("play_game...") await asyncio.sleep(1) print("play_game...") await asyncio.sleep(1) print('end play_game') return "游戏gg了"

async def dian_wai_mai(): """点外卖""" print("dian_wai_mai") await asyncio.sleep(1) print("wai_mai on the way...") await asyncio.sleep(1) print("wai_mai on the way...") await asyncio.sleep(1) print("wai_mai arrive") return "外卖到了"

async def main(): print("start main") future1 = dian_wai_mai() future2 = play_game() ret1 = await future1 ret2 = await future2 print(ret1, ret2) print("end main")

if name == 'main': t1 = time.time() loop = asyncio.get_event_loop() loop.run_until_complete(main()) t2 = time.time() print('cost:', t2-t1)

上述代码的运行结果如下:

start main dian_wai_mai wai_mai on the way... wai_mai on the way... wai_mai arrive start play_game play_game... play_game... end play_game 外卖到了 游戏gg了 end main cost: 6.0070815086375

总耗时6秒,一直傻等着外卖送到,才开始打游戏,不仅游戏凉凉了,外卖也凉了,这显然不是我们想要的效果为什么会这样呢?用await后它不应该自动切到别的协程吗?>用await确实会切换协程,但你事先没有告诉事件循环有哪些协程,它不知道切换到哪个协程,所以事件循环就会按顺序坚持执行完外卖协程再执行打游戏协程那怎么提前告诉事件循环有哪些协程呢?用asyncio.gather(),看代码(仅对main函数进行了修改)```pythonasyncdefmain():print("startmain")future1=dian_wai_mai()future2=play_game()ret1,ret2=awaitasyncio.gather(future1,future2)print(ret1,ret2)print("endmain")

再看看这次的执行结果:

startmaindian_wai_maistartplay_gamewai_maiontheway...play_game...wai_maiontheway...play_game...wai_maiarriveendplay_game外卖到了游戏gg了endmaincost:3.003592014312744

ok, 这次符合我们的预期了。

原文:https://juejin.cn/post/7095400034165850148

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com