写在前面
文中有较多的内容为转载,尽量指出转载来源。
1 进程(process)
定义:进程是正在运行程序的实例。
如chrome 进程的三种状态:
- 就绪态
- 执行态
- 阻塞态
进程是基于计算机系统的异常。进程切换是需要保存上下文环境(一些寄存器,以及栈的信息。 子进程和父进程具有相同的文件描述符。 不同的进程具有不同的地址空间,变量无法共享。调度有操作系统完成。process 由 process control block (PCB)控制 ;。
2 线程(thread)
一个进程,包含多个线程 线程是一种轻量进程,实际上在linux内核中,两者几乎没有差别,除了一点——线程并不产生新的地址空间和资源描述符表,而是复用父进程的。线程的调度和进程一样,都必须陷入内核态。调度有操作系统完成(thread 由 thread control blocks (TCBs)控制)。 线程模型主要通过陷入切换上下文。。
3 协程(coroutine)
一个线程,包含多协程。协程由应用程序实现调度,线程由操作系统实现调度,不需要陷入内核。
3.1 函数调用[2]
函数,所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
函数的调用 是通过栈来实现的,一个线程就是执行一个子函数 栈帧保存了给出代码的的信息和上下文,其中包含最后执行的指令,全局和局部命名空间,异常状态等信息。f_valueblock保存了数据,b_blockstack保存了异常和循环控制方法
def foo():
x = 1
def bar(y):
z = y + 2 # <--- (3) ... and the interpreter is here.
return z
return bar(x) # <--- (2) ... which is returning a call to bar ...
foo() # <--- (1) We're in the middle of a call to foo ...
那么,相应的调用栈如下,一个py文件,一个类,一个函数都是一个代码块,对应者一个Frame,保存着上下文环境以及字节码指令。
c ---------------------------
a | bar Frame | -> block stack: []
l | (newest) | -> data stack: [1, 2]
l ---------------------------
| foo Frame | -> block stack: []
s | | -> data stack: [<Function foo.<locals>.bar at 0x10d389680>, 1]
t ---------------------------
a | main (module) Frame | -> block stack: []
c | (oldest) | -> data stack: [<Function foo at 0x10d3540e0>]
k ---------------------------
携程看起来像函数,但是内部可以中断。举例如
def A():
print '1'
print '2'
print '3'
def B():
print 'x'
print 'y'
print 'z'
假设程序是由携程执行的,,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
1
2
x
y
3
z
3.2 协程的实现 [3]
-
并发模型
并发系统从本质上讲,是一系列独立的执行单元(routine)在调度器的调度之下交替执行。与线程相比,协程并发模型与其最大不同之处在于:协程由应用程序实现调度,线程由操作系统实现调度
由于协程作为执行单元并发执行时,会因为主动放弃执行权限而被挂起,调度系统必须同时维护多个函数执行上下文,以实现非本地跳转(non-local jump)。
C-Python解释器栈结构以及它是如何工作的。
def a(x):
b(x + 1)
def b(x):
c(x * x)
def c(x):
print 'x=',x
a(42)
在CPython shell中执行上面这段代码时,Python-Stack与 C-Stack结构如下图。
Python虚拟机以eval_code2作为解释函数执行a时,首先通PyFrame_New构造a的栈帧frame-a并返回eval_code2,然后执行a对应的Python代码。由于a嵌套调用b,此时解释器递归调用eval_code2并重复之前过程执行b,从而形成C-stack和由PyFrameObject构成的python-stack。
范式转换对于stackfull的标准Python而言,实现协程并发的核心在于将Python-Stack与 C-Stack解耦,这种改变Python解释器执行过程的方法也被称作范式转换。要点可以归纳为以下三个方面:
- 1.函数栈帧执行时机
解释器执行Python函数的标准范式是:为函数的PyCodeObject
构造一个函数栈帧PyFrameObject并附带所有参数,最后通过eval_code2
解释执行相应的函数体直到其返回。然而,以正确的调用顺序执行所有的函数栈帧并不意味着我们必须在当前C-stack嵌套层级中执行eval_code2。如果我们能够避免与C-stack相关的所有后续操作,就可以在函数栈帧执行前实现C-stack的退栈操作,从而达到解耦的目的。 - 2.参数生命周期
在标准python中,函数参数的引用由其上层调用者持有。这意味着只有下层函数返回后,其参数元组的引用才能被上层函数销毁。
现在,让我们换一种思维方式。很明显,函数参数应该与函数栈帧有着相同的生命周期,参数元组的引用也应该同函数栈帧一起被销毁。所以,我们在PyFrameObject结构中添加对参数元组的引用,就可以实现范式的转换。 - 3.系统状态
在标准python中,执行一个函数栈帧后的返回值会存在两种情况:返回PyObject:代表函数正常执行。
-
返回NULL:代表函数抛出异常。
基于这两种基本系统状态,添加一个特殊的返回值类型Py_UnwindToken
作为第三种系统状态,这样我们便可以在下层栈帧被执行之前实现C-stack
退栈操作。
由于Py_UnwindToken与其他Python对象兼容,这一范式的转换对于大部分相关代码并不可见,我们只需要对执行栈帧的C函数做出修改即可。Return Value系统状态: NULL:函数执行异常
Py_UnwindToken:调度函数栈帧
Other PyObject:作为正常结果返回
[2] 生成器的源码分析 http://www.cnblogs.com/coder2012/p/4990834.html
[3] Stackless Python 探秘 http://shymonk.com/posts/2016/06/stackless-python-tan-mi/