greenlet是如何实现的?Python使用C堆栈作为解释器,它堆分配Python堆栈帧,但除此之外,它如何分配/交换堆栈,它如何挂钩到解释器和函数调用机制,以及它如何与C扩展交互?(任何怪癖)?
在源代码的greenlet.c顶部有一些注释,但它们有点不透明.FWIW我是从一个不熟悉CPython内部人员但非常熟悉低级系统编程,C,线程,事件,协同程序/协作线程,内核编程等的人的角度出发的.
(一些数据点:它们不使用ucontext.h,它们在每个上下文切换时都会执行2x memcpy,alloc和free.)
Rab*_*eih 32
当python程序运行时,你基本上有两段代码在底层运行.
首先,CPython解释器C代码运行并使用标准C-stack来保存其内部堆栈帧.其次,实际的python解释了不使用C-stack的字节码,而是使用堆来保存其堆栈帧.greenlet只是标准的python代码,因此表现相同.
现在,在典型的微线程应用程序中,您将有数千甚至数百万的微线程(greenlets)在整个地方切换.每个开关本质上等同于具有延迟返回的函数调用(可以这么说),因此将使用一些堆栈.问题是,解释器的C堆栈迟早会遇到堆栈溢出.这正是greenlet扩展所针对的目的,它旨在将堆栈的各个部分来回移入堆中以避免此问题.
如你所知,greenlets有三个基本事件,一个spawn,一个switch和一个return,所以让我们依次看看:
A)一个产卵
新生成的greenlet与堆栈中的自己的基址相关联(我们当前所在的位置).除此之外,没有什么特别的事情发生 新生成的greenlet的python代码以正常方式使用堆,并且解释器像往常一样继续使用C-stack.
B)一个开关
当greenlet从交换greenlet切换到时,C-stack的相关部分(从switchng greenlet的基地址开始)被复制到堆中.复制的C堆栈区域被释放,并且切换的greenlet解释器先前保存的堆栈数据被从堆复制到新释放的C堆栈区域.切换的greenlet的python代码以正常方式继续使用堆.当然,扩展代码会跟踪所有这些(哪个堆部分转到哪个greenlet等等).
C)回归
堆栈未受影响,返回greenlet的堆区域由python垃圾收集器释放.
基本上就是这样,可以在(http://www.stackless.com/pipermail/stackless-dev/2004-March/000022.html)上找到更多细节和解释,或者只需阅读Alex答案中指出的代码即可.
Ale*_*lli 30
如果获得并研究greenlet的来源,你会看到在第greenlet.c
16行开头的长评论的顶部,其中包含以下摘要......:
PyGreenlet是一系列C堆栈地址,必须以这样的方式保存和恢复:当我们切换到它时,堆栈的整个范围都包含有效数据.
并继续第82行,总结你所询问的内容.你有没有研究这些系列(以及以下1000多个实施它们? - )......?我没有看到一种方法来进一步挤压这66条线,同时仍然有意义,在这里复制和粘贴它们也没有任何附加价值.
基本上,你会发现没有真正的"挂钩"可以说(C级堆栈在解释器的鼻子下来回切换,可以这么说),除了在多线程代码中与线程状态的微妙交互从堆栈中保存和恢复greenlet的状态是基于memcpy
调用加上一些调用Python内存管理器来分配/重新分配和释放来自或返回堆栈的空间.第227-295行中的三个函数处理繁琐的工作,它们被包含在298-310"的几个C宏中,以简化维护",正如其中的评论所述.
其他C扩展可以与greenlet扩展交互的接口在第956-1045行实现,并通过此处greenlet.h
记录的"CObject API"(当然,通过)公开.