协程原理深度剖析 进程线程协程原理一课通收藏
\/ itspcool 交流学习
进程,线程和协程
进程:一段程序的执行过程,资源分配和调度的基本单位,有其独立地址空间,互相之间不发生干扰
线程:轻量级进程,资源调度的最小单位,共享父进程地址空间和资源,其调度和进程一样要切换到内核态
并行:同时发生,在多核CPU中,多个任务可同时在不同CPU上面同一时间执行
并发:宏观并行,微观串行,操作系统根据相关算法,分配时间片来调度,从而达到一种宏观上并行的方式
上下文:程序执行的状态,通常用调用栈记录程序执行的当前状态以及其相关的环境信息
早期,CPU是单核,无法真正并行,为了产生共享CPU的假象,提出了时间片概念,将时间分割成连续的时间片段,多个程序交替获得CPU使用权限。 而管理时间片分配调度的调度器则成为操作系统的核心组件。
程序能交替执行,但上下文切换必然会引起程序相关变量混乱,因此在物理地址基础上提出虚拟地址概念
CPU增加内存管理单元,进行虚拟地址和物理地址的转换
操作系统加入内存管理模块,管理物理内存和虚拟内存
进程出现
进程是一个实体,包括程序代码以及其相关资源(内存,I/O,文件等),可被操作系统调度。但想一边操作I/O进行输入输出,一边想进行加减计算,就得两个进程,这样写代码,内存就爆表了。于是又想着能否有一轻量级进程呢,只执行程序,不需要独立的内存,I/O等资源,而是共享已有资源,于是产生了线程。
一个进程可以跑很多个线程处理并发,但是线程进行切换的时候,操作系统会产生中断,线程会切换到相应的内核态,并进行上下文的保存,这个过程不受上层控制,是操作系统进行管理。然而内核态线程会产生性能消耗,因此线程过多,并不一定提升程序执行的效率。正是由于1.线程的调度不能精确控制;2.线程的切换会产生性能消耗。协程出现了。
协程是一种轻量级的用户态线程 2.开发者自行控制程序切换时机,而不是像进程和线程那样把控制权交给操作系统 3.协程没有线程、进程切换的时间和资源开销 4.协程是非抢占式调度,当前协程切换到其他协程是由自己控制;线程则是时间片用完抢占时间片调度
生成器
协程的核心就是上下文切换,在Python中最简单的实现是用生成器
生成器有个方法 send() 可以从调用者向生成器函数发送数据,这样就可以在生成器中 yield future 表示要等待 future 的结果,然后把上下文切换到调用者,等 future 结果准备好后调用者再 send(future.result()) 给生成器发送结果,并把上下文切换到生成器函数
def generator_function():
# 正常情况应用loop.create_future()
result = yield asyncio.Future()
print('future结果:', result)
return 2
def main():
generator = generator_function()
try:
future = generator.send(None)
# 假设某个回调调用了future.set_result
future.set_result(1)
future = generator.send(future.result())
except StopIteration as e:
print('generator_function结果:', e.value)
输出:
future结果: 1
generator_function结果: 2
async, await
async def 定义的函数称为协程函数,它永远返回一个协程对象,即使函数里没有用到 await
await 后面可以跟一个 awaitable 对象,它的返回值是 awaitable 对象的结果。一个实现了 await() 方法的对象或者协程对象都是 awaitable 对象。await() 方法返回一个生成器(即这个方法是一个生成器函数),它的实现和上面的生成器协程一样, yield future 表示要等待future的结果。当执行协程遇到 await 时,流程控制交给后面的 awaitable 对象,直到最底层用 yield future 上下文才切换到调用者
为了使 await 兼容生成器实现的协程,可以用 @asyncio.coroutine 装饰器装饰 yield from 实现的协程(其实它就是给生成器函数加了个 flag CO_ITERABLE_COROUTINE)。生成器实现的协程返回的对象(生成器)没有 await() 方法,但它也是 awaitable 对象
协程对象和生成器一样实现了 send(), throw(), close() 方法,但是不可以直接迭代( await() 方法返回的生成器可以迭代),知道这个就可以实现手动执行协程了:
用 async, await 实现的协程
async def coroutine_function2():
# 正常情况应用loop.create_future()
result = await asyncio.Future()
print('future结果:', result)
return 2
async def coroutine_function():
result = await coroutine_function2()
print('coroutine_function2结果:', result)
return 3
def main():
coroutine = coroutine_function()
# 正常情况应用asyncio.ensure_future()执行协程
try:
future = coroutine.send(None)
# 假设某个回调调用了future.set_result
future.set_result(1)
future = coroutine.send(future.result())
except StopIteration as e:
print('coroutine_function结果:', e.value)
事件循环
其实事件循环本身跟协程没有什么关系,它只负责添加回调( call_soon, call_later ),维护 scheduled, ready 队列,在有事件时调用回调而已。这里不研究了,感兴趣的可以看它的实现,大部分在 asyncio.base_events.BaseEventLoop