一、什么是协程
协程(coroutine)可以理解为是线程的优化,又称之为轻量级进程。它是一种比线程更节省资源、效率更高的系统调度机制。
协程具有这样的特点,即在同时开启的多个任务中,一次只执行一个,只有当前任务遭遇阻塞,才会切换到下一个任务继续执行。这种机制可以实现多任务的同步,又能够成功地避免线程中使用锁的复杂性,简化了开发。
协程是程序员在自己的业务代码里实现的任务调度。
协程: 协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行.
二、简单代码示例讲解asynico程序
在python中协程通过async def关键字来定义一个协程。
-
协程函数: 定义形式为 [
async def
]的函数,await 用于挂起阻塞的异步调用接口。 - 协程对象: 调用 协程函数 所返回的对象。
- [
asyncio run()
]此函数会运行传入的协程,负责管理asynicio事件循环,终结异步生成器,并关闭线程池。此函数总是会创建一个新的事件循环并在结束时关闭。
import asyncio
import datetime
# 定义协程函数
async def coroutineTask1():
print(f"任务1开始执行, {datetime.datetime.now()}")
await asyncio.sleep(5)
print(f"任务1结束执行, {datetime.datetime.now()}")
# 定义协程函数
async def coroutineTask2():
print(f"任务2开始执行, {datetime.datetime.now()}")
await asyncio.sleep(3)
print(f"任务2结束执行, {datetime.datetime.now()}")
# 定义协程
async def main():
task1 = asyncio.create_task(coroutineTask1()) # coroutineTask1() 会生成协程对象,asyncio.create_task()把协程对象封装成任务
task2 = asyncio.create_task(coroutineTask2())
await task1 # await 用于挂起阻塞的异步调用接口
await task2
if __name__ == "__main__":
asyncio.run(main()) # 运行asyncio程序
运行结果如下:
任务1开始执行, 2020-12-24 10:43:11.119403
任务2开始执行, 2020-12-24 10:43:11.119487
任务2结束执行, 2020-12-24 10:43:14.122335
任务1结束执行, 2020-12-24 10:43:16.121067
Process finished with exit code 0
从运行结果可以看出任务1和任务2几乎同时运行,而任务2由于中间sleep(3)阻塞三秒,而任务1 sleep(5)阻塞5秒,所以任务2先完成。任务开始和结束的时间相差时间正是各自的sleep阻塞时间。
协程是多任务的顺序执行,只有当前任务挂起后,才会切换到其他任务来执行;而线程是以 CPU 轮换的方式执行。
三、事件循环
事件循环是每个asyncio的应用核心,事件循环会运行异步任务和回调,执行网络IO操作,以及运行子进程。通常建议应用开发者应当使用更高层级的asyncio函数,例如asyncio.run(),应当很少有必要引用循环对象或调用其方法。
3.1获取事件循环
asyncio.new_event_loop()
创建一个新的事件循环loop。
asyncio.set_event_loop(loop)
将loop设置为当前OS线程的当前事件循环。
asyncio.get_event_loop()
获取当前事件循环。如果当前os线程没有设置当前事件循环,该os线程为主线程,并且set_event_loop还没有被调用,则asyncio将创建一个新的事件循环并将其设置为当前事件循环。
asyncio.get_running_loop()
返回当前OS线程中正在运行的事件循环,如果没有正在运行的事件循环则会引发RuntimeError。此函数只能由协程或回调来调用。
3.2事件循环方法集
3.2.1创建任务对象(Future和Task)
任务对象是对协程的一种封装,其中包含各种状态,如阻塞状态(suspended),运行状态(running),完成状态(done);
loop.create_future()
创建一个附加到事件循环中的asyncio.Futrure对象。
loop.create_task(coro,*,name=None)
创建一个协程任务,返回一个Task对象,name参数不为空则使用Task.set_name()来设置任务的名称。
- Task派生至Future。
- Future 主要用于coroutine之间
执行权
的切换, 内部本质用的是yield, await future -> await -> yeild,这个类很大程度上模仿concurrent.futures.Future - Task将(coroutine,Future)整合到一起, 成为一个task, 然后交给loop去执行. add_done_callback可以为Task添加完成通知回调。
3.2.2运行和停止事件循环
loop.run_until_complete(Future)
运行直到future(Future的实例)被完成。如果参数是coroutine(协程)对象,将被隐拭调度为asyncio.Task来运行。
loop.stop()
停止事件循环
loop.is_running()
返回True如果当前事件循环正在运行
loop.is_close()
关闭事件循环。当这个函数调用的时候,循环必须处于非运行状态。pending状态的回调将被丢弃。
loop.shtdown_default_executor()
安排默认执行器的关闭并等待它合并ThreadPoolExecutor中的所有线程。注意:当使用asyncio.run()时不必调用此函数。
3.3新建事件循环启动协程并发的实例。
import asyncio
import datetime
async def coroutine_1(): # 定义协程函数
print(f"任务1开始执行, {datetime.datetime.now()}")
await asyncio.sleep(3)
print(f"任务1结束执行, {datetime.datetime.now()}")
async def coroutine_2(): # 定义协程函数
print(f"任务2开始执行, {datetime.datetime.now()}")
await asyncio.sleep(5)
print(f"任务2结束执行, {datetime.datetime.now()}")
if __name__ == "__main__":
loop = asyncio.new_event_loop() # 创建一个新的事件循环loop
asyncio.set_event_loop(loop) # 将loop设置为当前OS线程的当前事件循环
task = loop.create_task(coroutine_1()) # 创建任务
task = loop.create_task(coroutine_2()) # 创建任务
loop.run_until_complete(task) # 运行事件循环直到所有任务完成
运行结果如下:
任务1开始执行, 2020-12-24 15:32:16.941913
任务2开始执行, 2020-12-24 15:32:16.941995
任务1结束执行, 2020-12-24 15:32:19.945121
任务2结束执行, 2020-12-24 15:32:21.942709
Process finished with exit code 0
四、使用封装好的asyncio实现协程并发
4.1 在任务前使用await字段运行
import asyncio
import datetime
async def coroutine_1(): # 定义协程函数
print(f"任务1开始执行, {datetime.datetime.now()}")
await asyncio.sleep(3)
print(f"任务1结束执行, {datetime.datetime.now()}")
async def coroutine_2(): # 定义协程函数
print(f"任务2开始执行, {datetime.datetime.now()}")
await asyncio.sleep(5)
print(f"任务2结束执行, {datetime.datetime.now()}")
async def main():
await asyncio.create_task(coroutine_1())
await asyncio.create_task(coroutine_2())
if __name__ == "__main__":
asyncio.run(main())
运行结果:
任务1开始执行, 2020-12-24 17:09:13.100339
任务1结束执行, 2020-12-24 17:09:16.103835
任务2开始执行, 2020-12-24 17:09:16.104020
任务2结束执行, 2020-12-24 17:09:21.107280
Process finished with exit code 0
[asyncio.create_task(coro,*,name=None)
] 函数用来将coro协程打包为一个Task排入日程准备执行。
在python3.7之前改用asyncio.ensure_future()
函数
但我们发现是任务顺序执行的。
4.2并发运行任务
asyncio.gather(*aws,loop=None,return_exceptions=False)
如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。
如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。
如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。
如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。
如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会被取消。
import asyncio
import datetime
async def coroutine_1(): # 定义协程函数
print(f"任务1开始执行, {datetime.datetime.now()}")
await asyncio.sleep(3)
print(f"任务1结束执行, {datetime.datetime.now()}")
async def coroutine_2(): # 定义协程函数
print(f"任务2开始执行, {datetime.datetime.now()}")
await asyncio.sleep(5)
print(f"任务2结束执行, {datetime.datetime.now()}")
async def main():
await asyncio.gather(coroutine_1(), coroutine_2())
if __name__ == "__main__":
asyncio.run(main())
执行结果如下;
任务1开始执行, 2020-12-24 17:27:08.493391
任务2开始执行, 2020-12-24 17:27:08.493466
任务1结束执行, 2020-12-24 17:27:11.498582
任务2结束执行, 2020-12-24 17:27:13.498001
Process finished with exit code 0
网友评论