Pytho协程之asyncio、EventLoop、Task、Future

前言

这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题

于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。

微信小程序搜索:Python面试宝典

或可关注原创个人博客:https://lienze.tech

也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习

AsyncIO

说到实现协程,就不得不说asyncio模块

asyncio是Python3.4之后的协程模块,使用async/await语法,是目前多种Python协程异步框架首选的异步实现方法

协程可以(摘自官方):

  • 等待一个future结束,这个可以暂时理解为一个即将/正在执行过程的协程方法
  • 等待另一个协程(产生一个结果,或引发一个异常)
  • 产生一个结果给正在等它的协程
  • 引发一个异常给正在等它的协程

基本概念

要理解asyncio的原理, 需要理解如下几个概念: 协程、事件循环、future/task

其中协程就是用户自己定义的任务,事件循环负责监听事件和回调,future/task则主要负责管理回调,以及驱动协程


事件循环所做的全部工作就是等待事件发生,然后再将每个事件与我们已明确与所述事件类型匹配的函数进行匹配

EventLoop

EventLoop事件循环负责同时对多个事件进行监听,当监听到事件时,就调用对应的回调函数,进而驱动不同的任务

比如接下来要对协程进行调用的asyncio.run,其本质就是创建一个事件循环,然后一直运行事件循环,直到加入这个循环中的所有任务结束为止

Task

是Python中与事件循环进行交互的一种主要方式,创建Task,意思就是把协程封装成Task实例,并追踪协程的运行/完成状态,用于未来获取协程的结果

Task的核心作用是为了创建多个可以并发执行的任务


通过asyncio.create_task()创建Task,其次调用实现并发

async def func(i):
    print('start-%s' % i)
    await asyncio.sleep(1)
    print('over-%s' % i)

async def main():
    t1 = asyncio.create_task(func(1))
    t2 = asyncio.create_task(func(2))

    await t1
    await t2
asyncio.run(main())

""" 输出
start-1
start-2
over-1
over-2
"""
Future

Future,又称未来对象、期程对象,其本质上是一个容器,用于接受异步执行的结果,是线程不安全的

而之前的Task属于继承自Future,Future 相较于 Task 属于更底层的概念,在开发过程中用到的并不多


Furture 对象内部封装了一个_state,这个_state 维护着四种状态:PendingRunningDoneCancelled

如果变成Done完成,就不再等待,而是往后执行,事件循环凭借着四种状态(阻塞、运行态、完成、取消)对Future协程对象进行调度

awaitable

如果一个对象可以在await语句中使用,那么它就是可等待(awaitable)对象;许多asyncio API都被设计为接受可等待对象

可等待对象有三种主要类型: 协程TaskFuture.

编写协程

asyncio通过asyncawait进行协程方法的编写以及调用

协程的定义,需要使用async def语句

import asyncio

async def function():
    print('这是一个协程方法')
    return 1

async def main():
    await function()
    return 0

验证某个方法是否为协程,可以通过asyncio.iscoroutinefunction(main)

>>> asyncio.iscoroutinefunction(main)
>>> True

调用协程

直接对协程进行函数调用,不会让其真正的调用,而是返回一个coroutine对象,即协程,这一点与生成器函数类似

可以通过如下方法进行协成任务的执行调用


asyncio.run: 这个方法经常用来执行最高层级的类似上面的main这样的起点协程方法

import asyncio

...
asyncio.run(main())

await: 在另一个已经运行的协程中用await等待它,await语句必须在async方法中使用

import asyncio

...
async def main():
    await function() # 这里
    return 0

并发调用协程

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)

并发运行aws序列中的协程tasksfuture,如果return_exceptionsTrue,异常会和成功的结果一样处理,并聚合至结果列表


首先设计一个函数用以输出当前时间

import datetime

def current_time():
    #  获取当前时间
    return datetime.datetime.now().strftime("%H:%M:%S")

接着是一个用以批量创建的协程方法,非常简单,只是用来输出运行、执行结束、及返回

在执行过程,还添加了一个休眠时间进行阻塞

async def func(sleep_time):
    print(f"[{current_time()}] 执行函数 {func.__name__}-{sleep_time}")
    await asyncio.sleep(sleep_time)
    print(f"[{current_time()}] 执行完毕 {func.__name__}-{sleep_time} ")
    return f"函数 {func.__name__}-{sleep_time}"

如果是一个个函数非并发执行结束,那么打印效果,可能类似如下

[11:15:32] 执行异步函数 func-0
[11:15:32] 函数 func-0 执行完毕
[11:15:32] 执行异步函数 func-1
[11:15:33] 函数 func-1 执行完毕
...

创建一定数量的func对应的协程

async def run():
    task_list = []
    for i in range(5):
        task = asyncio.create_task(func(i))
        task_list.append(task)

    done = await asyncio.gather(*task_list) # 并发调用
    print(done) # 输出结果

执行

def main():
    asyncio.run(run())

if __name__ == '__main__':
    main()

并发的执行后,结果是这样的

[11:30:54] 执行函数 func-0
[11:30:54] 执行函数 func-1
[11:30:54] 执行函数 func-2
[11:30:54] 执行函数 func-3
[11:30:54] 执行函数 func-4
[11:30:54] 执行完毕 func-0 
[11:30:55] 执行完毕 func-1 
[11:30:56] 执行完毕 func-2 
[11:30:57] 执行完毕 func-3 
[11:30:58] 执行完毕 func-4 
['函数 func-0', '函数 func-1', '函数 func-2', '函数 func-3', '函数 func-4']

等待并发

coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

asyncio.wait同样可以并发执行aws序列中的可等待对象,当满足return_when参数指定条件时,将会返回

timeout: [float/int],则它将被用于控制返回之前等待的最长秒数

return_when: 返回的条件

  • FIRST_COMPLETED: 函数将在任意可等待对象结束或取消时返回
  • FIRST_EXCEPTION: 函数将在任意可等待对象因引发异常而结束时返回
  • ALL_COMPLETED: 函数将在所有可等待对象结束或取消时返回

返回结果为两个集合,分别代表着执行结束阻塞


还是上一个示例所用到的一些基本函数

并发的函数

import asyncio

async def func(sleep_time):
    print(f"[{current_time()}] 执行函数 {func.__name__}-{sleep_time}")
    await asyncio.sleep(sleep_time)
    print(f"[{current_time()}] 执行完毕 {func.__name__}-{sleep_time} ")

async def run():
    task_list = []
    for i in range(2):
        task = asyncio.create_task(func(i))
        task_list.append(task)

    done, pending = await asyncio.wait(task_list, return_when=FIRST_COMPLETED)
    print(done)
    print(pending)

def main():
    asyncio.run(run())

if __name__ == '__main__':
    main()

当第一个任务执行结束之后,由于FIRST_COMPLETED的设置,已经可以拿到donepending结果

最终的输出如下

[12:01:57] 执行函数 func-0
[12:01:57] 执行函数 func-1
[12:01:57] 执行完毕 func-0 
{<Task finished name='Task-2' coro=<func() done, defined at /Users/zege/Desktop/1.py:11> result='函数 func-0'>}
{<Task pending name='Task-3' coro=<func() running at /Users/zege/Desktop/1.py:13> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10b1fc670>()]>>}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李恩泽的技术博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值