Python编程:asyncio库实现协程异步I/O并发 - 基础概念

Python编程:asyncio库实现协程异步I/O并发 - 基础概念

asyncio 库最早由PyPl提供,在 Python3.4 被引入到标准库中,我学习 asyncio 的时候 Python 已经更新3.8,此时 asyncio 的 API 已经是定格下来了,相对于老版本来说,增加了不少语法糖,更加方便的实现异步编程,推荐将 Python 更新至 3.7 以上进行学习。

因为 GIL(全局解释锁) 的存在,Python 在一次运行中,只能执行一个进程,这导致了IO阻塞以及代码运行速度很慢的问题,虽然有很多基于 多进程/多线程 的第三方库,但依旧弥补不了 Python 性能上的先天不足,而 asyncio 基于协程异步I/O实现事件的并发执行,不依赖 gevent,打破了GIL对性能的限制,你只需要通过 async / await 语法就能构建原生的协程代码了。

从一个简单的 Hello World 示例开始协程之旅:

import asyncio

async def main():    # 使用 async 关键字来声明这是一个协程(coroutines)
    print('Hello')
    # await 关键字能够挂起 coroutines 的执行以等待一个 awaitable 对象
    await asyncio.sleep(1)    # 等待 1s
    print('World')
    
asyncio.run(main())    # 使用 .run() 函数执行一个协程
# 运行结果:
--> Hello
--> World

在这个示例中,我们用 async 关键字将一个函数声明成协程,这个函数会打印一个 Hello ,并等待 1s 后继续打印 World,值得注意的是,协程无法通过 main() 调用,需要使用 asyncio.run(main()) 才能够运行协程,你可以理解为协程/协程对象的入口,理想状态下应该只被调用一次。

如果一个对象可以在 await 语句中使用,那么它就是一个 可等待(awaitable) 对象,可等待对象有三种主要的类型:协程,任务 和 Future。

​协程

​Python 协程属于 可等待 对象,因此可以在其他协程中被等待:

import asyncio
import time

async def hello():    # 定义一个 hello 协程
    await asyncio.sleep(1)
    print('Hello')
   
async def world():    # 定义一个 world 协程
    await asyncio.sleep(2)
    print('World')
   
async def main():    # 定义一个 main 协程
    print(f'started at {time.strftime("%X")}')
    await hello()
    await world()    # 在 main 协程中等待运行 协程hello() 和 协程world()
    print(f'finished at {time.strftime("%X")}')
   
asyncio.run(main())    # 执行 main() 协程
# 运行结果:
--> started at 14:54:25
--> Hello
--> World
--> finished at 14:54:28    # 打印之间相隔 3s

注意,在 asyncio 官方文档中,协程用来表示两个紧密关联的概念:
​协程函数:用 async 关键字声明的协程函数
​协程对象:调用协程函数所返回的对象

任务

当一个协程通过 asyncio.create_task() 等函数被打包为一个任务(task)时,该协程将自动排入日程,在上面的协程示例中,我们在 main() 中写入两个协程,从打印的时间上可以看出,这两个协程会先后执行,接下来我们尝试将这个行为打包成任务再运行:

import asyncio
import time

async def hello():    # 定义一个 hello 协程
    await asyncio.sleep(1)
    print('Hello1')
    await asyncio.sleep(2)
    print('Hello3')
   
async def world():    # 定义一个 world 协程
    await asyncio.sleep(2)
    print('World2')
   
async def main():    # 定义一个 main 协程
    task1 = asyncio.create_task(hello())    # 将 hello 打包成任务 task1
    task2 = asyncio.create_task(world())    # 将 world 打包成任务 task2
    print(f'started at {time.strftime("%X")}')
    await task1
    await task2    # 等待运行 task1 / task2
    print(f'finished at {time.strftime("%X")}')
   
asyncio.run(main())    # 执行 main() 协程
# 运行结果:
--> started at 14:57:17
--> Hello1
--> World2
--> Hello3
--> finished at 14:57:20    # 打印之间相隔 3s

从运行结果发现,我们可以通过 asyncio.create_task() 函数来打包任务实现并发运作多个协程,asyncio.sleep() 是 asyncio 内置的休眠协程,它会挂起当前任务,以允许其他任务的允许,asyncio 还提供了另外一种并发运行任务的方式,就是通过 asyncio.gather():

import asyncio
import time

async def hello():    # 定义一个 hello 协程
    await asyncio.sleep(1)
    print('Hello1')
    await asyncio.sleep(2)
    print('Hello3')
   
async def world():    # 定义一个 world 协程
    await asyncio.sleep(2)
    print('World2')
   
async def main():    # 定义一个 main 协程
    print(f'started at {time.strftime("%X")}')
    await asyncio.gather(
        hello(),
        world()    # 接收一组可等待对象aws
    )
    print(f'finished at {time.strftime("%X")}')
   
asyncio.run(main())    # 执行 main() 协程
# 运行结果:
--> started at 15:12:23
--> Hello1
--> World2
--> Hello3
--> finished at 15:12:26    # 打印之间相隔 3s

如果 gather() 接收的 aws 序列中某个可等待对象为协程,那它将自动转换成一个任务加入并发,如果 aws 序列中的任一 Task 或 Future 对象被取消,它会抛出一个 CancelledError 错误类型,但是这不会取消 gather() 的调用,防止当一个已经提交的 Tasks / Future 被取消后导致其他 Tasks / Future 也被取消掉。

Futures

我将其称为事务,事务(Future) 是一种特殊的 底层级 可等待对象,它表示一个异步操作的 最终结果,当一个 Future 对象被等待,则意味着协程将保持等待,直到该 Future 对象在其他地方运行完毕或者被取消掉,通常 Future 用于底层回调式代码与高层异步 / 等待式代码交互。

协程(coroture)、任务(Task) 和 事务(Future) 都可以看作一个广义的协程,Task 可以理解为是使用 asyncio.create_task() 等函数将一个 coroture 打包成的,而 Future 则是使用 asyncio.gather() 函数将复数的 Task(上文提到 coroture 会被 gather() 自动转换成Task) 和 Future 打包而成的一个大协程,而这些可等待对象都可以被 await 所接受。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值