了解python协程与异步编程
一、了解下并发和并行的区别
二、了解下python中线程和协程的区别
举个例子
假设有1个洗衣房,里面有10台洗衣机,有一个洗衣工在负责这10台洗衣机。
那么洗衣房就相当于1个进程,洗衣工就相当1个线程。
如果有10个洗衣工,就相当于10个线程,1个进程是可以开多线程的。这就是多线程!
那么协程呢?
先不急。大家都知道,洗衣机洗衣服是需要等待时间的,如果10个洗衣工,1人负责1台洗衣机,这样效率肯定会提高,但是不觉得浪费资源吗?
明明1 个人能做的事,却要10个人来做。只是把衣服放进去,打开开关,就没事做了,等衣服洗好再拿出来就可以了。
就算很多人来洗衣服,1个人也足以应付了,开好第一台洗衣机,在等待的时候去开第二台洗衣机,再开第三台,……直到有衣服洗好了,就回来把衣服取出来,
接着再取另一台的(哪台洗好先就取哪台,所以协程是无序的)。这就是计算机的协程!洗衣机就是执行的方法。”
三、协程的概念
协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。例如:
def func1():
print(1)
...
print(2)
def func2():
print(3)
...
print(4)
func1()
func2()
上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4。但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4 。
在Python中有多种方式可以实现协程,例如:
- greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现)
- yield,生成器,借助生成器的特点也可以实现协程代码。
- asyncio,在Python3.4中引入的模块用于编写协程代码。
- async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。****
下面一起来熟悉几个概念:
1协程函数
普通函数前加async,就是一个协程函数,如下
async def async_fun():
print('我是一个异步函数')
print(async_fun())
af.send(None)
-------执行结果--------
<coroutine object async_fun at 0x0000026F8E8CD940>
E:/bf_20220725//demo.py:533: RuntimeWarning: coroutine 'async_fun' was never awaited
print(async_fun())
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
我是一个异步函数
注意:异步函数不能直接被执行,返回结果是一个协程对象,可以想生成器一样去使用.send()方法调用
2协程对象
异步函数调用后返回的就是一个协程对象,见1、异步函数
3事件循环
事件循环是每个 asyncio 应用的核心。 事件循环会运行异步任务和回调,执行网络 IO 操作,以及运行子进程
事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环。
# 伪代码
任务列表 = [ 任务1, 任务2, 任务3,... ]
while True:
可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
for 就绪任务 in 已准备就绪的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表:
在任务列表中移除 已完成的任务
如果 任务列表 中的任务都已完成,则终止循环
在编写程序时候可以通过如下代码来获取和创建事件循环。
import asyncio
loop = asyncio.get_event_loop()
四 异步编程asyncio
1运行一个协程的方法
-
使用asyncio.run() 函数用来运行最高层级的入口点 "main()" 函数
程序中,如果想要执行协程函数的内部代码,需要 事件循环 和 协程对象 配合才能实现,如:
import asyncio
async def func():
print("协程内部代码")
# 调用协程函数,返回一个协程对象。
result = func()
# 方式一
# loop = asyncio.get_event_loop() # 创建一个事件循环
# loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。
# 方式二
# 本质上方式一是一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。
# asyncio.run 函数在 Python 3.7 中加入 asyncio 模块,
asyncio.run(result)
这个过程可以简单理解为:将协程当做任务添加到 事件循环 的任务列表,然后事件循环检测列表中的协程是否 已准备就绪(默认可理解为就绪状态),如果准备就绪则执行其内部代码。(当前事件循环中只有result一个TASK)
-
使用await 等待一个协程
以下代码段会在等待 1 秒后打印 "hello",然后 再次 等待 2 秒后打印 "world"
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
-
asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
2.await
await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码。
**await 后面只能跟可以被等待的对象
2.1可等待对象**
-
协程
Python 协程属于 可等待 对象,因此可以在其他协程中被等待:
-
任务
任务 被用来“并行的”调度协程
当一个协程通过 asyncio.create_task() 等函数被封装为一个 任务,该协程会被自动调度执行,即会自动在事件循环中创建一个task -
future
Future 是一种特殊的 低层级 可等待对象,表示一个异步操作的 最终结果。
当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕
2.2 可等到对象案例演示
实例1 等待对象是一个协程:
import asyncio
async def nested():
return 42
async def main():
print(await nested()) # will print "42".
asyncio.run(main())
总结:当await后面跟着一个协程对象对象时,直接执行协程内部的代码,当遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
实例2 等待对象是一个task
import asyncio
async def nested():
return 42
async def main():
task = asyncio.create_task(nested())
await task
asyncio.run(main())
总结:asyncio.create_task(协程对象)的方式创建Task对象,本质上是将协程对象封装成task对象,并将协程立即加入事件循环,同时追踪协程的状态
注意:asyncio.create_task() 函数在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。
实例3:等待对象是一个future(一般不会用到)
import asyncio
async def set_after(fut):
await asyncio.sleep(2)
fut.set_result("666")
async def main():
# 获取当前事件循环
loop = asyncio.get_running_loop()
# 创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候结束。
fut = loop.create_future()
# 创建一个任务(Task对象),绑定了set_after函数,函数内部在2s之后,会给fut赋值。
# 即手动设置future任务的最终结果,那么fut就可以结束了。
await loop.create_task(set_after(fut))
# 等待 Future对象获取 最终结果,否则一直等下去
data = await fut
print(data)
asyncio.run(main())
**总结:Future对象本身函数进行绑定,所以想要让事件循环获取Future的结果,则需要手动设置。而Task对象继承了Future对象,其实就对Future进行扩展,他可以实现在对应绑定的函数执行完成之后,自动执行set_result,从而实现自动结束。
2.3 接收异步函数返回值
编程过程中,如果我们想接收异步函数的返回值使 await + 协程函数/任务/future
五 实战案例演示
协程main函数定义两个await(后跟协程对象)
mport asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
总结:事件循环中始终只有main一个任务,所以遇到IO也无法进行切换,其实还是同步执行
改进上面案例,协程main函数定义两个await(后跟任务)
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
await task1
await task2
print(f"finished at {time.strftime('%X')}")
总结:asyncio.create_task()可以将协程对象加入到事件循环列表中,例子创建了两个任务,当执行task1时遇到IO堵塞,事件循环会调度执行task2,从而达到并发的效果
假如我们有很多个任务,总不能都像这样await 任务这样去写吧,这样会很傻,asyncio提供了wait和gather这两个方法假如我们有很多个任务,总不能都像这样await 任务这样去写吧,这样会很傻,asyncio提供了wait和gather这两个方法
1 asyncio.gather(*aws, return_exceptions=False)
如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致
import asyncio
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def main():
print("main开始")
task_list = [
asyncio.create_task(func(), name="n1"),
asyncio.create_task(func(), name="n2")
]
print("main结束")
result = await asyncio.gather(*task_list)
print(result)
asyncio.run(main())
-----返回结果------
main开始
main结束
1
1
2
2
['返回值', '返回值']
2asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)
返回两个 Task/Future 集合: (done, pending)。
用法:
done, pending = await asyncio.wait(aws)
如指定 timeout (float 或 int 类型) 则它将被用于控制返回之前等待的最长秒数。
请注意此函数不会引发 asyncio.TimeoutError。当超时发生时,未完成的 Future 或 Task 将在指定秒数后被返回。
import asyncio
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def main():
print("main开始")
task_list = [
asyncio.create_task(func(), name="n1"),
asyncio.create_task(func(), name="n2")
]
print("main结束")
done, pending = await asyncio.wait(task_list, timeout=None)
print(done, pending)
asyncio.run(main())
---------执行结果---------
main开始
main结束
1
1
2
2
{<Task finished name='n1' coro=<func() done, defined at E:\demo.py:574> result='返回值'>,
<Task finished name='n2' coro=<func() done, defined at E:\demo.py:574> result='返回值'>} set()
3 使用协程对接口实现高并发案例:
可以看到500和请求在3ms内全部处理完成了,效果还是不错的!
参考文档:https://pythonav.com/wiki/detail/6/91/#3.2.2 await
参考文档:https://docs.python.org/zh-cn/3/library/asyncio-task.html#coroutines
参考视频:https://www.bilibili.com/video/BV1Xq4y1D7Fs?spm_id_from=333.337.search-card.all.click&vd_source=a651f08d17caa1088efe9aca8af3670e
参考视频:https://www.bilibili.com/video/BV1oa411b7c9/?spm_id_from=333.788
参考视频:https://www.bilibili.com/video/BV1dD4y127bD?p=9&spm_id_from=pageDriver&vd_source=a651f08d17caa1088efe9aca8af3670e