greenlets如何运作?

时间:2010-07-28 00:27:43

标签: python

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。)

2 个答案:

答案 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