什么是协程?
协程,本质上还是单进程单线程运行的程序,并不能提升运算速度,但是适用于网络通讯,因为网络通讯中,经常会碰到等待的情况。
asyncio
看下面一段代码:
import asyncio
async def main():
print('hello')
await asyncio.sleep(1)
print('world')
coro = main()
asyncio.run(main())
- event_loop:核心组件,相当于一个大脑,他来决定该执行哪个任务。就像上面说到的,同时执行的任务只能有一个,不存在系统级的上下文切换。
- task:event_loop来执行
- coroutine:分为coroutine function、coroutine object:
上面的main函数就是一个coroutine function,由async修饰。不像其他函数,这个被async修饰的函数,被调用的时候返回的是一个coroutine object,也就是coro变量是一个coroutine function,执行coro赋值,并不会打印“hello world”。 那我们要怎么运行coroutine object呢?上面的代码中,asyncio.run(main())
就是把coroutine object变成task交由event_loop来执行,得到结果。
再来看这段代码:
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, 'hello')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
这一段会执行3秒,为什么呢?await也会把一个coroutine object变成task,交由event_loop来执行,并返回结果。event_loop不能从task中抢占使用权,只有等task执行结束或者调用await的时候才会返回控制权。上面这段代码和不使用await,async关键词的效果是一样的,他的运行过程如下:首先asyncio.run()会把main函数变成一个task交由event_loop来执行,执行过程中,遇到say_after(1, ‘hello’),await又把say_after(1, ‘hello’)变成一个task放到event_loop中,这个时候就执行say_after(1, ‘hello’),遇到asyncio.sleep(delay),那么await再把asyncio.sleep(delay)变成task,放到event_loop中。要继续执行say_after(1, ‘hello’),要等asyncio.sleep(delay)完成,那么我们就执行asyncio.sleep(delay),执行完,在执行say_after(1, ‘hello’),打印输出。就相当于一个函数栈。
还有一段代码:
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'hello'))
print(f"started at {time.strftime('%X')}")
await task1
await task2
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
执行到task1赋值时,控制权依然在event_loop中,执行完task1赋值,还能继续执行task2创建,创建完task2,这个时候event_loop中有两个task了。直到运行到await task1时,提交任务,执行函数。在函数体内部await asyncio.sleep(delay)时,需要等待,这个时候event_loop发现任务队列中还有一个任务等着执行,那么他就去执行另一个任务,也就是task2。因此,这一段代码运行只需要2秒。
这两段代码的区别就是,在执行task2,也就是say_after(2, ‘hello’),event_loop中是否已经知道了有这个任务。如果没有,那么就不会再task1等待的时候调过来执行say_after2,如果有,就会。
总结
- 为什么要协程?
线程之间的切换,线程之间的切换也是需要时间的。而协程是基于单线程单进程的思想构建出来的,这能够解决线程切换耗时的问题(相对来说),减少系统开销,提高运行性能。
- 线程适用于像网络通讯这种需要等待I/O的任务。