协程

概念

协程是运行在单线程上的”并发“

在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似yield操作。(最初协程就是使用生成器generator实现的)

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。
因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

优点

  1. 无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能
  2. 无需原子操作锁定及同步的开销
  3. 方便切换控制流,简化编程模型
  4. 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点

  1. 无法利用多核资源,协程需要和进程配合才能运行在多CPU上.当然绝大部分应用都没有这个必要,除非是cpu密集型应用
  2. 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

定义协程

  1. 使用关键字async定义协程函数

  2. 使用协程函数创建协程对象

  3. 创建一个事件循环

  4. 将协程对象注册到事件循环中

    import asyncio
    
    # 定义协程函数
    async def do_work(n):
    	print(n)
    
    # 创建协程对象    
    coroutine = do_work(2)	
    # 创建一个事件循环
    loop = asyncio.get_event_loop()
    #将协程对象注册到事件循环中
    loop.run_until_complete(coroutine)
    

创建一个任务

协程对象不能直接运行,在注册事件循环的时候,其实是run_until_completet将协程对象包装成了任务(task)对象

task对象是future的子类,保存了协程运行后的状态,用于未来获取协程的结果

  1. 创建方法:实质上就是对协程对象进行了一下包装,成future对象

    1. asyncio.ensure_future(协程对象)

    2. asyncio.create_task(协程对象)

    3. 那么用 ensure_future 还是 create_task 呢?先对比一下函数声明:

      asyncio.ensure_future(coro_or_future, *, loop=None)
      BaseEventLoop.create_task(coro)
      

      显然,ensure_future 除了接受 coroutine 作为参数,还接受 future 作为参数。
      看 ensure_future 的代码,会发现 ensure_future 内部在某些条件下会调用 create_task,综上所述:

      • encure_future: 最高层的函数,推荐使用
      • create_task: 在确定参数是 coroutine 的情况下可以使用。
  2. run_until_complete可以使用future对象

    import asyncio
    
    async def do_work(n):
    	print(n)
      
    coroutine = do_work(2)	
    loop = asyncio.get_event_loop()
    # 创建任务
    task = asyncio.ensure_future(coroutine)
    # task = asyncio.create_task(coroutine)
    print(task)
    #将任务对象注册到事件循环中
    loop.run_until_complete(task)
    print(task)
    
    """
    结果:
    <Task pending coro=<do_work() running at F:/Work/RabbitMQ/temp.py:4>>
    2
    <Task finished coro=<do_work() done, defined at F:/Work/RabbitMQ/temp.py:4> result=None>
    """
    

    从结果可以看出任务的状态,pending:就绪,finished:结束

绑定回调

可以看到上述的协程函数是没有返回值的,要是有返回值(一般是执行结果),如果获取到协程的返回值呢?

在task执行结束之后可以获取执行结果,使用回调函数获取协程返回值,回调函数的参数中必须有一个future对象

  1. 定义回调函数

  2. 在任务对象中添加回调函数

    import asyncio
    
    async def do_work(n):
    	print(n)
        return n+1
    
    # 定义回调函数
    def callback(future):
        print("Callback:",future.result())
      
    coroutine = do_work(2)	
    loop = asyncio.get_event_loop()
    # 创建任务
    task = asyncio.ensure_future(coroutine)
    
    # 在任务对象中添加回调函数
    task.add_done_callback(callback)
    
    loop.run_until_complete(task)
    
    """
    Callback: 3
    
    也可以直接使用task.result()获取返回结果
    """
    

阻塞

使用await可以针对耗时操作进行挂起,就像yield一样,函数让出控制权,执行别的协程,知道其他协程挂起或者计数,再回来执行。

import asyncio

async def do_work(n):
    print("Waiting:",n)
    # await 说明此处阻塞挂起
    await asyncio.sleep(n)
    return f'Done after {n}s'

coroutine = do_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
print(task.result())

并发

  1. 创建多个协程对象
  2. 将多个协程对象放到任务列表(tasks)中
  3. asyncio.wait(tasks),等待任务完成
import asyncio

async def do_work(n):
    print("Waiting:",n)
    # await 说明此处阻塞挂起
    await asyncio.sleep(n)
    return f'Done after {n}s'

# 创建多个协程对象
coroutine1 = do_work(2)
coroutine2 = do_work(3)
coroutine3 = do_work(4)

loop = asyncio.get_event_loop()
# 将多个协程对象放到任务列表中
tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]
# 并发执行多个协程
loop.run_until_complete(asyncio.wait(tasks))
# 遍历任务结果
for i in tasks:
    print(i.result())
    
"""
整体耗时4秒左右,如果不是使用协程的话,则耗时为2+3+4=9秒左右
"""    

协程嵌套

  1. 将协程对象创建、任务列表封装、获取返回结果等动作放到一个协程函数中
  2. 主函数->master()->do_work()
import asyncio


async def do_work(n):
    print("Waiting:", n)
    await asyncio.sleep(n)
    return f'Done after {n}s'


async def master():
    """
    将协程对象创建和加入任务列表的动作封装到一个协程函数中
    """
    coroutine1 = do_work(2)
    coroutine2 = do_work(3)
    coroutine3 = do_work(4)

    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3)
    ]

    # 第一种后去返回结果的方式
    # dones,pending = await asycnio.wait(tasks)
    # for i in dones:
    #    print(i.result())

    # 第二种获取返回结果的方式
    # results = await asyncio.gather(*tasks)
    # for result in results:
    #     print(result)
    # 第三种获取返回结果的方式
    # return await asyncio.gather(*tasks)
	# 第四种获取返回结果的方式
	for task in asyncio.as_completed(tasks):
        result = await task
        print(result)

loop = asyncio.get_event_loop()
# 第三种获取返回结果的方式
#results = loop.run_until_complete(master())
#for result in results:
#    print(result)
loop.run_until_complete(master())


"""
第四种返回结果的方式与前三个不同,会逐个输出,而前三个是一并输出
"""

协程停止

future对象的状态

  1. Pending
  2. Running
  3. Done
  4. Cancelled

如果需要停止事件循环,需要先把task取消。使用asyncio.Task获取事件循环的task

  1. 获取事件循环的task,asyncio.Task.all_tasks()获取所有任务的信息
  2. 取消任务
  3. 停止事件循环
  4. 开启事件循环(不开启会报错)
  5. 关闭事件循环
import asyncio

async def do_work(n):
    print("Waiting:", n)
    await asyncio.sleep(n)
    return f'Done after {n}s'

coroutine1 = do_work(2)
coroutine2 = do_work(3)
coroutine3 = do_work(4)

tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]

loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
    print(asyncio.Task.all_tasks())	# 打印所有任务当前的信息
    
    for task in asyncio.Task.all_tasks():
        print(task.cancel())    # 如果返回True代表取消成功
    # 或者使用
    # print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
    # 但是这样结果只有一个Ture,说明所有任务都结束了
    
    loop.stop()
finally:
    loop.close()


"""
Waiting: 2
Waiting: 3
Waiting: 4
{<Task pending coro=<do_work() running at temp.py:6> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000016931DBE8E8>()]> cb=[_wait.<locals>._on_completion() at D:\P
ython37\lib\asyncio\tasks.py:466]>, 
<Task pending coro=<do_work() running at temp.py:6> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000016931DBEF18>()]> cb=[_wai
t.<locals>._on_completion() at D:\Python37\lib\asyncio\tasks.py:466]>, 
<Task pending coro=<do_work() running at temp.py:6> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000016931FD3588>()]> cb=[_wait.<locals>._on_completion() at D:\Python37\lib\asyncio\tasks.py:466]>, 
<Task pending coro=<wait() running at D:\Python37\lib\asyncio\tasks.py:389>
wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000016932002AC8>()]>>}
True
True
True
True

看结果,当出现强制停止任务时,会打印出任务的状态,以及任务取消成功与否
"""
posted @ 2021-09-10 21:08  注入灵魂  阅读(98)  评论(0)    收藏  举报