协程
协程是轻量级线程,拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。
协程的应用场景:I/O 密集型任务。这一点与多线程有些类似,但协程调用是在一个线程内进行的,是单线程,切换的开销小,因此效率上略高于多线程。当程序在执行 I/O 时操作时,CPU 是空闲的,此时可以充分利用 CPU 的时间片来处理其他任务。在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 None )。 有了协程,我们在函数的执行过程中,如果遇到了耗时的 I/O 操作,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回控制权。
定义协程
Python3.4 加入了协程的概念,以生成器对象为基础,在 Python3.5 则增加了关键字 async/await,使得协程的实现更加方便。Python 中使用协程最常用的库莫过于 asyncio ,接下来我们以 asyncio 为基础来介绍协程的使用。
先看一个简单的例子来理解
1 import asyncio
2 import time
3
4
5 async def task():
6 print(f"{time.strftime('%H:%M:%S')} task 开始 ")
7 time.sleep(2)
8 print(f"{time.strftime('%H:%M:%S')} task 结束")
9
10
11 coroutine = task()
12 print(f"{time.strftime('%H:%M:%S')} 产生协程对象 {coroutine},函数并未被调用")
13 loop = asyncio.get_event_loop()
14 print(f"{time.strftime('%H:%M:%S')} 开始调用协程任务")
15 start = time.time()
16 loop.run_until_complete(coroutine)
17 end = time.time()
18 print(f"{time.strftime('%H:%M:%S')} 结束调用协程任务, 耗时{end - start} 秒")
19
运行结果如下所示
22:34:06 产生协程对象 <coroutine object task at 0x0000025B8CE62200>,函数并未被调用
22:34:06 开始调用协程任务
22:34:06 task 开始
22:34:08 task 结束
22:34:08 结束调用协程任务, 耗时2.015564203262329 秒
说明:首先引入 asyncio ,这样才可以使用 async 和 await 关键字( async 定义一个协程, await 用来临时挂起一个函数或方法的执行),接着我们使用 async 定义一协程方法,
随后我们直接调用了这个方法,然而这个方法并没有执行,而是返回了一个 coroutine 协程对象。随后我们使用 get_event_loop() 方法创建一个事件循环 loop ,并调用了 loop 对象的 run_until_complete() 方法将协程注册到事件循环 loop 中,然后启动。最后我们才看到了 task 方法打印了输出结果。
注意: async 定义的方法无法直接执行,必须将其注册到事件循环中才可以执行。
我们还可以为任务绑定回调函数:
1 import asyncio
2 import time
3
4
5 async def _task():
6 print(f"{time.strftime('%H:%M:%S')} task 开始 ")
7 time.sleep(2)
8 print(f"{time.strftime('%H:%M:%S')} task 结束")
9 return "运行结束"
10
11
12 def callback(task):
13 print(f"{time.strftime('%H:%M:%S')} 回调函数开始运行")
14 print(f"状态:{task.result()}")
15
16
17 coroutine = _task()
18 print(f"{time.strftime('%H:%M:%S')} 产生协程对象 {coroutine},函数并未被调用"