美文网首页
1.19Python之协程

1.19Python之协程

作者: Benedict清水 | 来源:发表于2020-12-24 16:04 被阅读0次

一、什么是协程

协程(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

相关文章

网友评论

      本文标题:1.19Python之协程

      本文链接:https://www.haomeiwen.com/subject/upgynktx.html