greenlet如何工作?

greenlet如何实现? Python使用C栈作为解释器,它堆 – 分配Python栈帧,但除此之外,它是如何分配/交换栈,它是如何挂钩到解释器和函数调用机制,以及它如何与C扩展交互? (任何怪癖)?

源代码中greenlet.c的顶部有一些注释,但它们有点不透明。 FWIW我来自一个不熟悉CPython内部但熟悉底层系统编程,C,线程,事件,协程/合作线程,内核编程等等的人。

(有些数据点:它们不使用ucontext.h ,它们在每个上下文切换上执行2次memcpy,alloc和free 。)

当一个python程序运行时,你基本上有两块代码在运行。

首先,CPython解释器C代码运行并使用标准的C栈来保存其内部的栈帧。 其次,实际的python解释了不使用C栈的字节码,而是使用堆来保存它的栈帧。 greenlet只是标准的python代码,因此performance相同。

现在,在一个典型的微线程应用程序中,如果不是数百万个微线程(greenlet)切换到全部位置,您将拥有数千个微线程。 每个开关本质上等同于带有延迟返回的函数调用(可以这么说),因此将使用一些堆栈。 问题是,解释器的C栈会迟早堆栈溢出。 这正是greenlet扩展所针对的目的,它是为了避免这个问题而devise的,来回移动堆栈的堆栈和堆栈。

如你所知,greenlet,spawn,switch和return有三个基本事件,所以让我们来看看这些:

A)一个产卵

新产生的greenlet与堆栈中的基地址(我们目前所在的地方)相关联。 除此之外,没有什么特别的事。 新生成的greenlet的python代码以正常的方式使用堆,解释器像往常一样继续使用C堆栈。

B)一个开关

当一个greenlet被切换到greenlet时,C-stack的相关部分(从switchng greenlet的基地址开始)被复制到堆中。 复制的C堆栈区域被释放,并且切换的greenlet的解释器先前保存的堆栈数据被从堆复制到新释放的C堆栈区域。 切换的greenlet的python代码以正常的方式继续使用堆。 当然,扩展代码会logging所有这些(堆段落到哪个greenlet等等)。

C)回报

堆栈是不变的,返回的greenlet的堆区被python垃圾收集器释放。

基本上就是这样,更多的细节和解释可以在( http://www.stackless.com/pipermail/stackless-dev/2004-March/000022.html )find,或者通过阅读Alex的答案中指出的代码。

如果得到并学习greenlet的源代码 ,你会在greenlet.c的顶部greenlet.c一个很长的注释,从第16行开始,总结如下:

PyGreenlet是一个C堆栈地址范围,必须以这样的方式保存和恢复:当我们切换到堆栈时,堆栈的全部范围都包含有效的数据。

并继续到第82行,总结你正在问什么。 你有没有研究这些线(以及以下1000 +实施他们; – )…? 我看不出有什么办法可以进一步挤压这66条线,同时还有意义,在这里复制和粘贴它们也没有任何附加价值。

基本上,你会发现没有真正的“挂钩”可言(C层堆栈在“解释者的鼻子下面”来回切换,可以这么说),除了multithreading代码中与线程状态的微妙交互,从堆栈保存和恢复greenlet的状态是基于memcpy调用,加上一些调用Python内存pipe理器来分配/重新分配和释放来自或返回堆栈的空间。 第227-295行中的三个函数处理这个咕噜咕噜的工作,为了简化维护,它们被包装在一对298-310的Cmacros中,就像那里的评论所说的那样。

其他C扩展可以通过它与greenlet扩展交互的接口在956-1045行实现,并通过这里logging的“CObject API”(当然是greenlet.hgreenlet.h