异步携程

1. 异步协程(coroutine)介绍

协程不同于进程和线程(正好这俩我也不懂),是一种特殊的处理方式,允许一个线程在遇到IO等待时间线程不会傻傻等待,利用空闲的时候再去干点其他事情。在python的发展中有四种实现协程的方法,分别是

  • greenlet 早期第三方模块
  • yield关键字
  • asynico 装饰器(py 3.4引入)
  • async, await关键字(py3.5) ⭐️ 最新,推荐​

1.1 协程的原理

协程的原理就是在执行第一个耗时的函数的时候,去开启执行第二个耗时的函数,直到两个函数都结束。

伪代码的演示如下

def func1():
    print(1)
    ... # 耗时步骤1
    print(2)
    
def func2():
    print(3)
    ... # 耗时步骤2
    print(4)

func1()
func2()

首先运行func1(),在输出1后,运行到耗时步骤1,立刻跳转到func2(),在输出3后,遇到耗时步骤2,此时陷入等待,看哪个耗时步骤执行完,如果有另一个func,则去执行另一个。

假设两个耗时步骤时间一样长,并且只有这两个func,那么预计的输出就是

1
3
2
4

这里以greenlet的方法讲解协程如何去启动另一个函数
要使用该模块,首先需要pip install greenlet

from greenlet import greenlet

def func1():
    print(1) # 第1步:输出1
    gr2.switch() # 第3步:切换到func2函数
    print(2) # 第6步:输出2	
    gr2.switch() # 第7步:切换到func2函数,从上一次执行的位置继续向后执行
    
def func2():
    print(3) # 第4步∶输出3
    gr1.switch() # 第5步:切换到func1函数,从上一次执行的位置继续向后执行
    print(4) # 第8步:输出4

# 生成了特殊的对象,先不会执行
gr1 = greenlet(func1)
gr2 = greenlet(func2)

gr1.switch()	#第1步︰去执行func1函数

greenlet中,启动另一个函数是手动完成的,也就是switch()方法,需要启动哪个对象,就调用他的该方法。

上述讲解了协程的基本概念,但是无法运用到实际中,因为人根本无法精准控制什么时候需要跳转去执行另一个函数,因此需要引入async协程框架

1.2 async协程框架

async协程框架的好处就是,一切都是自动执行的,只要代码运行到一个需要等待的协程对象的时候,就会自动去启动另一个协同对象。这里介绍协程编程中最重要的两个关键字,asyncawait,以及一些框架的用法。

首先需要下载该模块,pip install asyncio

1.2.1 async

async的作用是用来定义协程函数,就是在之前的函数定义之前加了async而已,如下面代码所示

# 定义协程函数
async def func():
    print('???')

当定义了协程函数后,它就变得和函数不一样了。在“调用”协程函数的时候,如今只会产生一个携程对象,而不会直接执行函数内部的代码了。

# 得到协程对象
result = func()

在我们平常写的同步代码中,都是执行完一个方法(比如 def函数、 with open)后再执行另一个方法的,而async关键字的含义就是将方法变成能够被中断的、能够被挂起的(这俩等价),这样就为协程提供了可能性。

参考https://www.cnblogs.com/xinghun85/p/9937741.html

假设有两个异步函数async a,async b,a中的某一步有await,当程序碰到关键字await b()后,异步程序挂起后去执行另一个异步b程序,就是从函数内部跳出去执行其他函数,当挂起条件消失后,不管b是否执行完,要马上从b程序中跳出来(此句存疑),回到原程序执行原来的操作。如果await后面跟的b函数不是异步函数,那么操作就只能等b执行完再返回,无法在b执行的过程中返回。

1.2.2 await

await用来将一个方法进行挂起,当执行到该方法中的耗时操作时,就会挂起该方法,等待执行结束,并同时启动另一个方法上述这段文字可以描述成事件循环,将会在接下来介绍。await方法后面+可等待的对象,主要是三类,分别是携程对象, Future, Task协程对象就是执行加了async的函数得到的,上面介绍过了,接下来介绍剩下两种

1.2.2.1 事件循环

所有被await的对象都会被装载到任务列表中,构建成一个事件循环。事件循环也是协程之所以能够运作的底层原因,所有在列表中的任务都有自己的状态,分为已完成,IO等待中,可执行,正在执行..等。在事件循环的时候,首先会检查任务列表中的所有任务,将可执行已完成的任务分别返回成列表。对于可执行的任务,将会依次开始执行,对于已经完成的任务,则会从事件循环中移除,直到任务列表为空结束。

伪代码如下。

# 伪代码

# 任务都有自己的状态:分为 已完成,IO等待中,可执行,正在执行..等
任务列表=[任务1, 任务2, 任务3, ...]

# 死循环 检查每个任务
while True:
	可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,'可执行''已完成'的任务返回
    
	for 就绪任务 in 可执行的任务列表:
        执行已就绪的任务
        
	for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除已完成的任务
        
如果 任务列表 中的任务都已完成,则终止循环
1.2.2.2 Task对象

TaskFuture携程对象一样,都是可被加到任务列表中的任务

Tasks are used to schedule coroutines concurrently.

When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon。

白话:在事件循环中添加多个任务。

Tasks用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环(接下来会讲)中等待被调度执行。除了使用 asyncio.create_task() 函数以外,还可以用低层级的 loop.create_task()ensure_future() 函数。不建议手动实例化 Task 对象。

注意:asyncio.create_task() 函数在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。

总之,记住这是一个用于事件循环的成员类型就行了。

创建Task的代码如下所示

# 这里是 > Python 3.7 的版本的语法
asyncio.create_task( 协程对象 )

# 这里是 Python 3.6 的版本的语法
loop = asyncio.get_event_loop() # 获取事件循环
loop.create_task( 协程对象 )

# 或者
asyncio.create_task

可以运行的示例代码如下所示,包括了事件循环的创建Task的创建,这里没有显式体现任务列表

import asyncio

# 获取事件循环
loop = asyncio.get_event_loop()

# 协程函数
async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"

async def main():
    print("main开始")
	
    # 创建Task对象,将当前执行func函数任务添加到事件循环。
    task1 = loop.create_task( func() )
	
    # 创建Task对象,将当前执行func函数任务添加到事件循环。
    task2 = loop.create_task( func() )

    print("main结束")

    # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
    # 此处的await是等待相对应的协程全都执行完毕并获取结果
    # 这里的两个await相当于将两个task隐式地加入到了列表中
    # ret就是函数的返回值
    ret1 = await task1
    ret2 = await task2
    print(ret1, ret2)

# 执行
loop.run_until_complete(main())

这段代码中显式体现了循环列表,是真的声明了一个list列表,然后封装成了携程对象。封装的函数会返回两个值,分别对应着成功执行的任务的返回值,和没法成功执行的任务

import asyncio

loop = asyncio.get_event_loop()


async def func1():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"

async def func2():
    print(3)
    await asyncio.sleep(3)
    print(4)
    return "返回值"

async def main():
    print("main开始")
    
    # 显式声明列表
    task_list = [
        # asyncio.create_task(func(), name='n1'),
        loop.create_task(func1()),
        loop.create_task(func2())
    ]
    print("main结束")

    # 由于await后面只能加 协程对象,Future、Task对象,因此需要将列表封装成携程对象
    
    # 两个默认返回
    # donw:是个集合,两个task的返回值都在这里
    # pending:没啥意义
    # 设置最多等2s,如果么有完成,pending就是还没完成的那些
    # 如果设置为None,就是等待所有的都完成
    done,pending = await asyncio.wait(task_list, timeout=3)
    print(done, pending)

loop.run_until_complete(main())

也可以直接用一个普通的列表,不用async main直接执行

import asyncio

loop = asyncio.get_event_loop()

async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"

# 不创建task
task_list = [
    func(),
	func(), 
]

# 这样子在生成事件循环之后,内部会创建一个
done,pending = loop.run_until_complete(asyncio.wait(task_list))
print(done)
1.2.2.3 Future(asyncio.Future)对象

FutureTask携程对象一样,都是可被加到列表中的任务

A Futureis a special low-level awaitable object that represents an eventual result of an asynchronous operation.

Task继承自Future,Task对象内部await结果的处理基于Future对象来的。实际上,Future和Task的用法几乎一样

示例代码 (暂无可用的)

async def main():
    # 获取当前事件循环
    loop = asyncio.get_running_loop()

    # 创建一个任务(Future对象),这个任务什么都不干。
    fut = loop.create_future()

    # 等待任务最终结果(Future对象),没有结果则会一直等下去。
    await fut

asyncio.run( main() )
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值