【Python】对async与await的理解
对python async与await的理解
async/await关键字是出现在python3.4以后。网上已经有很多文章对async/await这两个关键字都有讲解,包括如何由python2的yield from发展到async/await这两个关键字,以及一些代码实现都有。
但是对于像我这样初次接触的人来说,光看代码分析也不一定能理解,我也是在度娘上搜索很多相关的网站,当中也有官网,都没有发现能让我一眼看懂在什么地方可以用await,什么情况用await的文章。
经过自己的重新思考,总算对async、await有一些初步的了解,所以想把自己的理解记录下来,希望对一些学习协程或者异步的初学者也有一定的帮助。
对于网上能搜到的一些代码实现、例子,这里就不重复了。
一、首先要知道什么是协程、异步
举个例子:假设有1个洗衣房,里面有10台洗衣机,有一个洗衣工在负责这10台洗衣机。那么洗衣房就相当于1个进程,洗衣工就相当1个线程。如果有10个洗衣工,就相当于10个线程,1个进程是可以开多线程的。这就是多线程!
那么协程呢?先不急。大家都知道,洗衣机洗衣服是需要等待时间的,如果10个洗衣工,1人负责1台洗衣机,这样效率肯定会提高,但是不觉得浪费资源吗?明明1 个人能做的事,却要10个人来做。只是把衣服放进去,打开开关,就没事做了,等衣服洗好再拿出来就可以了。就算很多人来洗衣服,1个人也足以应付了,开好第一台洗衣机,在等待的时候去开第二台洗衣机,再开第三台,……直到有衣服洗好了,就回来把衣服取出来,接着再取另一台的(哪台洗好先就取哪台,所以协程是无序的)。这就是计算机的协程!洗衣机就是执行的方法。
当你程序中方法需要等待时间的话,就可以用协程,效率高,消耗资源少。
好了!现在来总结一下:
1 | 洗衣房 ==> 进程 |
二、async/await 的使用
正常的函数在执行时是不会中断的,所以你要写一个能够中断的函数,就需要添加async关键。
- async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行。
- await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。
await 后面只能跟异步程序或有__await__属性的对象,因为异步程序与一般程序不同。
假设有两个异步函数async a,async b,当异步函数a中的某一步有await,当程序碰到关键字await func()后,异步程序挂起后去执行另一个异步b程序,就是从函数内部跳出去执行其他函数,当挂起条件消失后,不管async b是否执行完,都要马上从b程序中跳出来,回到原程序a执行原来的操作。
如果await后面跟的func函数不是异步函数,那么操作就只能等func执行完再返回,无法在func执行的过程中返回。如果要在func执行完才返回,也就不需要用await关键字了,直接调用b函数就行。所以这就需要await后面跟的是异步函数了。在一个异步函数中,可以不止一次挂起,也就是可以用多个await。
三、实例
1 | async def test2(i): |
输出结果:
1 | https://segmentfault.com/p/1210000013564725 |
对于下面这几行代码:
1 | loop = asyncio.get_event_loop() |
可以在网上找到具体讲解,在这可用下图来粗略形容一下

当所有的异步程序运行完就会返回最后结果。
对于什么是task和future,asyncio.wait()与asyncio.gather()、asyncio.ensure_future()这些大家可以网上找到解释。
说到这里,相信你也大概清楚在什么时候用async、什么时候await了吧! 如果有说得不对的地方,请多多指正!!
1 | 版权声明:本文为CSDN博主「MaNong125」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 |
四、慕雪的小结
上面原博主的那一堆文字不好理解,慕雪对此总结了一下:
同步函数调用
我们最常见的python写法,是这样的
1 | def func_1(): |
在这种调用方式中,func1和func2必须顺序执行,这也是最基础的编程法则。
当func1和func2没有关系的时候,我们就可以引入多线程,并行处理两个函数,加快效率。
1 | import threading |
此时会创建两个独立的子线程,分别执行func1和func2,这两个子线程互不干扰,完成各自的任务。每个子线程执行func1和func2的时候,中途都不会被任何操作打断。执行func1里面的thread_time_task()函数时,也不会被任何流程打断。
异步函数调用
到异步这里,情况就不一样了。还是一样的两个操作,并给func1新增一个耗时的任务逻辑time_task(),代码如下
1 | import asyncio |
此时的流程就会出现变化:
- 假设程序先进入
async def func_1(),开始执行func_1()的逻辑; - 遇到
await time_task(),会开始执行time_task()函数里面的逻辑,但此时可以不需要等待time_task()函数完全执行完毕,就可以去执行其他工作,比如执行async def func_2(); - 当
await time_task()函数执行完毕后,程序就会立马从async def func_2()回到async def func_1(),继续执行async def func_1()的剩余print步骤 - 因为print函数不是异步函数,没有await,所以执行print函数的步骤不再会被打断,此时
async def func_1()会执行完毕,退出这个函数 - 程序回到没有执行完毕的
async def func_2(),继续执行await asyncio.sleep(10)的步骤 async def func_1()执行完毕,整个流程结束
在这个流程中,我们的“执行流”会在func1和func2之间跳跃,func1和func2并没有“同步”执行完毕,而是会异步的交错执行。
但,如果你的函数本来就是一个同步的函数,比如下面这种情况,此时给程序加上aysnc和await没有任何意义,并不是加上async和await就能加快程序运行速度的!
1 | import asyncio |
异步和多线程的区别
下表格列出了多线程和异步逻辑的区别
| 特性 | async/await | 多线程 |
|---|---|---|
| 所属模型 | 异步/非阻塞 I/O 模型 | 并行执行模型 |
| 目的 | 提高 I/O 密集型任务的效率(如网络、文件读写) | 利用多核 CPU 并行处理计算密集型任务 |
| 是否真正并行 | 不一定(单线程中也可使用 async/await) | 是(多个线程可同时运行在不同 CPU 核心上) |
相比于多线程,异步逻辑依旧只有一个线程,只是让这个线程去一心多用,管理多个任务流,从而提高程序运行的效率。
但由于异步只有一个线程,所以始终只会运行在某一个cpu的核心上,无法利用cpu多核计算的优势,对于计算密集型的任务,请使用多线程,因为这种场景下使用异步只会降低程序运行效率!
但对于io密集型的场景,使用异步就比较好了,性能会比多线程更高,因为节省了系统去执行用户态到内核态的相互切换,处理内核空间里面父子线程状态管理的开销。整个异步的流程,都是由我们用户态程序里面的异步管理器执行的,每个异步线程之间的切换是不涉及到内核态的逻辑的!



