greenlets如何实施? Python使用C堆栈作为解释器,它堆分配Python堆栈帧,但除此之外,它如何分配/交换堆栈,它如何挂钩到解释器和函数调用机制,以及它如何与C扩展交互? (任何怪癖)?
源代码中greenlet.c顶部有一些注释,但它们有点不透明。 FWIW我是从一个不熟悉CPython内部人员但非常熟悉低级系统编程,C,线程,事件,协同程序/协作线程,内核编程等的人的角度出发的。
(有些数据点:他们don't use ucontext.h和他们do 2x memcpy, alloc, and free on every context switch。)
答案 0 :(得分:32)
当python程序运行时,你基本上有两段代码在底层运行。
首先,CPython解释器C代码运行并使用标准C-stack来保存其内部堆栈帧。其次,实际的python解释了不使用C-stack的字节码,而是使用堆来保存其堆栈帧。 greenlet只是标准的python代码,因此表现相同。
现在,在典型的微线程应用程序中,您将有数千甚至数百万的微线程(greenlets)在整个地方切换。每个开关本质上等同于具有延迟返回的函数调用(可以这么说),因此将使用一些堆栈。问题是,解释器的C堆栈迟早会遇到堆栈溢出。这正是greenlet扩展所针对的目的,它旨在将堆栈的堆栈来回移入堆中以避免此问题。
如你所知,greenlets有三个基本事件,一个spawn,一个switch和一个return,所以让我们依次看看:
A)一个Spawn
新生成的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答案中指出的代码即可。
答案 1 :(得分:30)
如果获取并研究greenlet的sources,您会在greenlet.c
的顶部看到一条长评论,该评论从第16行开始,其中包含以下摘要...:
PyGreenlet是一系列C堆栈 必须保存的地址 以这样的方式恢复了满满的 堆栈范围包含有效数据 当我们切换到它时。
并继续第82行,总结你所询问的内容。你有没有研究这些系列(以及以下1000多个实施它们? - )......?我没有看到一种方法来进一步挤压这66条线,同时仍然有意义,在这里复制和粘贴它们也没有任何附加价值。
基本上,你会发现没有真正的“挂钩”可以说(C级堆栈在“解释器的鼻子下”来回切换,可以这么说)除了与多线程中的线程状态的微妙交互-threaded代码,以及从堆栈中保存和恢复greenlet的状态是基于memcpy
调用以及对Python内存管理器的一些调用,以分配/重新分配和释放来自或返回的空间,堆栈。第227-295行中的三个函数处理了繁琐的工作,它们被包含在298-310“的几个C宏中,以简化维护”,正如那里的评论所说。
其他C扩展可以与greenlet扩展交互的接口在第956-1045行实现,并通过“CObject API”(当然通过greenlet.h
)公开记录here。