前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 异步 async/await -1.一文理解什么是协程

python 异步 async/await -1.一文理解什么是协程

作者头像
上海-悠悠
发布2022-05-13 13:12:03
4.7K0
发布2022-05-13 13:12:03
举报
文章被收录于专栏:从零开始学自动化测试

前言

Python 在 3.5 版本中引入了关于协程的语法糖 async 和 await, 在 python3.7 版本可以通过 asyncio.run() 运行一个协程。 所以建议大家学习协程的时候使用 python3.7+ 版本,本文示例代码在 python3.8 上运行的。

什么是协程?

网上有个关于洗衣机的例子,写的挺好的,借用下

代码语言:javascript
复制
假设有1个洗衣房,里面有10台洗衣机,有一个洗衣工在负责这10台洗衣机。那么洗衣房就相当于1个进程,洗衣工就相当1个线程。如果有10个洗衣工,就相当于10个线程,1个进程是可以开多线程的。这就是多线程!**那么协程呢?**
先不急。大家都知道,洗衣机洗衣服是需要等待时间的,如果10个洗衣工,1人负责1台洗衣机,这样效率肯定会提高,但是不觉得浪费资源吗?明明1 个人能做的事,却要10个人来做。只是把衣服放进去,打开开关,就没事做了,等衣服洗好再拿出来就可以了。就算很多人来洗衣服,1个人也足以应付了,开好第一台洗衣机,在等待的时候去开第二台洗衣机,再开第三台,……直到有衣服洗好了,就回来把衣服取出来,
接着再取另一台的(哪台洗好先就取哪台,所以协程是无序的)。这就是计算机的协程!洗衣机就是执行的方法。”

协程,又称微线程。 协程的作用是在执行函数A时可以随时中断去执行函数B,然后中断函数B继续执行函数A(可以自由切换)。 但这一过程并不是函数调用,这一整个过程看似像多线程,然而协程只有一个线程执行。

协程很适合处理IO密集型程序的效率问题。协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,因此对于CPU密集型程序协程需要和多进程配合。

进程与线程

  • 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在早期面向进程设计的计算机结构中,进程是程序的基本执行实体; 在当代面向线程设计的计算机结构中,进程是线程的容器。 程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 线程(thread)是操作系统能够进行运算调度的最小单位。 它被包含在进程之中,是进程中的实际运作单位。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
  • 一个进程可以有很多线程,每条线程并行执行不同的任务。

洗衣机例子

假设有3台洗衣机在工作,每个洗衣机工作的时长不一样

代码语言:javascript
复制
import time

def washing1():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

def washing2():
    time.sleep(8)
    print('washer2 finished')

def washing3():
    time.sleep(5)
    print('washer3 finished')

if __name__ == '__main__':
    start_time = time.time()
    washing1()
    washing2()
    washing3()
    end_time = time.time()
    print('总共耗时:{}'.format(end_time-start_time))

我们通过函数的方式调用,3个函数,总共耗时16 秒!

这里函数的执行方式是同步运行的,于是这里需要知道一个概念:

同步/异步

  • 同步: 在发出一个同步调用时,在没有得到结果之前,该调用就不返回。
  • 异步: 在发出一个异步调用后,调用者不会立刻得到结果,该调用就返回了。

再举个小学生在学校学习的一个案例: 小明同学的妈妈给他早上安排了三件事: 1.洗衣机洗衣服需要花 15 分钟, 2.电饭煲做饭需要花 20 分钟, 3.做作业需要花 25 分钟 那么请问:小明同学早上完成以上三件事需要花多久??? 这个大家肯定都知道是25分钟,因为在做作业的时候,可以先按下洗衣机和电饭煲的按钮,不用等它完成,洗衣机和电饭煲做好了会发出‘滴滴滴’的声音通知你。

所以这三件事是可以异步完成的,这就是异步的魅力!

协程(异步)

协程(coroutines)通过 async/await 语法进行声明,是编写 asyncio 应用的推荐方式。 这里我们需要学一个新的语法糖 async, 例如,以下代码段(需要 Python 3.7+)

代码语言:javascript
复制
import time

async def washing1():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

async def washing2():
    time.sleep(8)
    print('washer2 finished')

async def washing3():
    time.sleep(5)
    print('washer3 finished')

if __name__ == '__main__':
    start_time = time.time()
    washing1()
    washing2()
    washing3()
    end_time = time.time()
    print('总共耗时:{}'.format(end_time-start_time))

如果我们直接当函数运行,会出现警告,并且并没有只需函数里面的print内容

代码语言:javascript
复制
untimeWarning: coroutine 'washing1' was never awaited
  washing1()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

先看下async 定义的异步函数到底返回的是什么

代码语言:javascript
复制
import time

async def fun():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了


res = fun()
print(res)   # <coroutine object fun at 0x000001FA1882B9C0>

返回的是coroutine object 也就是协程对象,并没直接执行

执行协程 coroutine 函数

执行协程函数,必须使用事件循环get_event_loop()

代码语言:javascript
复制
import time
import asyncio

async def fun():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

coroutine_1 = fun()  # 协程是一个对象,不能直接运行
loop = asyncio.get_event_loop()  # 创建一个事件循环
result = loop.run_until_complete(coroutine_1)  # 将协程对象加入到事件循环中,并执行

在python3.7+以后的版本,可以直接asyncio.run()去执行一个协程函数

代码语言:javascript
复制
import time
import asyncio

async def fun():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

coroutine_1 = fun()  # 协程是一个对象,不能直接运行
asyncio.run(coroutine_1)

多个任务执行 asyncio.create_task()

当我们需要3台洗衣机一起来工作,这时候需要创建多个任务,也就是会用到asyncio.create_task()

代码语言:javascript
复制
import time
import asyncio

async def washing1():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

async def washing2():
    time.sleep(8)
    print('washer2 finished')

async def washing3():
    time.sleep(5)
    print('washer3 finished')

async def main():
    print('start main:')
    start_time = time.time()
    task1 = asyncio.create_task(washing1())
    task2 = asyncio.create_task(washing2())
    task3 = asyncio.create_task(washing3())
    await task1
    await task2
    await task3
    end_time = time.time()
    print('-----------end main----------')
    print('总共耗时:{}'.format(end_time-start_time))

if __name__ == '__main__':
    # asyncio.run(main())
    loop = asyncio.get_event_loop()  # 创建一个事件循环
    result = loop.run_until_complete(main())  # 将协程对象加入到事件循环中,并执行

运行结果:

代码语言:javascript
复制
start main:
washer1 finished
washer2 finished
washer3 finished
-----------end main----------
总共耗时:16.000632286071777

我们发现运行的总耗时还是16秒,并没有达到我们想要的结果,最大耗时8秒,这个问题稍后再讲,先解决如果有很多个任务,那我们总不会一直写

代码语言:javascript
复制
    task1 = asyncio.create_task(washing1())
    task2 = asyncio.create_task(washing2())
    task3 = asyncio.create_task(washing3())
    await task1
    await task2
    await task3

这里可以优化下

代码语言:javascript
复制
import time
import asyncio

async def washing1():
    time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

async def washing2():
    time.sleep(8)
    print('washer2 finished')

async def washing3():
    time.sleep(5)
    print('washer3 finished')

if __name__ == '__main__':
    print('start main:')
    start_time = time.time()
    # step1 创建一个事件循环
    loop = asyncio.get_event_loop()
    # step2 将异步函数(协程)加入事件队列
    tasks = [
        washing1(),
        washing2(),
        washing3()
    ]
    # step3 执行事件队列 直到最晚的一个事件被处理完毕后结束
    loop.run_until_complete(asyncio.wait(tasks))
    end_time = time.time()
    print('-----------end main----------')
    print('总共耗时:{}'.format(end_time-start_time))

优化后,运行结果

代码语言:javascript
复制
start main:
washer3 finished
washer1 finished
washer2 finished
-----------end main----------
总共耗时:16.00227665901184

await 使用

上面虽然异步执行了三个任务,但是时间并没减少,主要是因为 time.sleep() 是阻塞的,需换成异步的

代码语言:javascript
复制
import time
import asyncio

async def washing1():
    await asyncio.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

async def washing2():
    await asyncio.sleep(8)
    print('washer2 finished')

async def washing3():
    await asyncio.sleep(5)
    print('washer3 finished')

if __name__ == '__main__':
    print('start main:')
    start_time = time.time()
    # step1 创建一个事件循环
    loop = asyncio.get_event_loop()
    # step2 将异步函数(协程)加入事件队列
    tasks = [
        washing1(),
        washing2(),
        washing3()
    ]
    # step3 执行事件队列 直到最晚的一个事件被处理完毕后结束
    loop.run_until_complete(asyncio.wait(tasks))
    end_time = time.time()
    print('-----------end main----------')
    print('总共耗时:{}'.format(end_time-start_time))

这样就可以达到我们的预期,总共耗时是8秒了

代码语言:javascript
复制
start main:
washer1 finished
washer3 finished
washer2 finished
-----------end main----------
总共耗时:8.002010822296143

接着我们在看下 await 如何使用, 当我们直接 await time.sleep(3)

代码语言:javascript
复制
import time
import asyncio

async def washing1():
    await time.sleep(3)  # 第一台洗衣机,
    print('washer1 finished')  # 洗完了

coroutine_1 = washing1() # 协程是一个对象,不能直接运行
loop = asyncio.get_event_loop()  # 创建一个事件循环
result = loop.run_until_complete(coroutine_1)  # 将协程对象加入到事件循环中,并执行

运行会报错:TypeError: object NoneType can’t be used in ‘await’ expression

代码语言:javascript
复制
Traceback (most recent call last):
  File "D:/demo/a6.py", line 11, in <module>
    result = loop.run_until_complete(coroutine_1)  # 将协程对象加入到事件循环中,并执行
  File "D:\python3.8\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "D:/demo/a6.py", line 6, in washing1
    await time.sleep(3)  # 第一台洗衣机,
TypeError: object NoneType can't be used in 'await' expression

因为 await 后面必须要是一个可等待对象

  • await + 可等待对象(协程对象,Future,Task对象(IO等待))
  • 等待到对象的返回结果,才会继续执行后续代码

可等待对象 await 的使用

可等待对象:如果一个对象可以在 await 语句中使用,那么它就是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象。 可等待 对象有三种主要类型: 协程, 任务 和 Future . 协程:python中的协程属于 可等待 对象,所以可以在其他协程中被等待

接着我们再把洗衣机工作的场景分2个步骤实现,第一个步骤是放衣服,第二个步骤是洗衣机工作

代码语言:javascript
复制
import time
import asyncio

async def add_clothes():
    print('往洗衣机添加衣服....')
    await asyncio.sleep(2)       # 模拟这个任务耗时2秒

async def washing1():
    print('洗衣机工作之前,需加衣服进去')
    await add_clothes()  # 等待这个事情完成
    print('衣服加进去,可以开始工作了。。。。')
    await asyncio.sleep(3)  # 模拟洗衣机工作的耗时
    print('washer1 finished')  # 洗完了

print('start washing:')
start_time = time.time()
coroutine_1 = washing1() # 协程是一个对象,不能直接运行
loop = asyncio.get_event_loop()  # 创建一个事件循环
result = loop.run_until_complete(coroutine_1)  # 将协程对象加入到事件循环中,并执行
end_time = time.time()
print('-----------end washing----------')
print('总共耗时:{}'.format(end_time-start_time))

运行结果

代码语言:javascript
复制
start washing:
洗衣机工作之前,需加衣服进去
往洗衣机添加衣服....
衣服加进去,可以开始工作了。。。。washer1 finished
-----------end washing----------
总共耗时:5.001740217208862

往洗衣机加衣服和洗衣机工作这2个事情,它是需要等第一件事完成才能执行,所以这2个任务是需要等待完成才能做下一步的。 2个洗衣机工作,是互不影响的,所以不需要等第一个洗衣机工作完成,2个洗衣机工作的任务是异步的。

2022年第 1 期《Python 测试平台开发》课程

2022年第 10 期《python接口web自动化+测试开发》课程,2月13号开学

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 从零开始学自动化测试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是协程?
  • 洗衣机例子
  • 协程(异步)
  • 执行协程 coroutine 函数
  • 多个任务执行 asyncio.create_task()
  • await 使用
  • 可等待对象 await 的使用
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档