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代码,因此表现相同。
现在,在一个典型的微线程应用程序中,如果不是数百万个微线程(greenlet)切换到全部位置,您将拥有数千个微线程。 每个开关本质上等同于带有延迟返回的函数调用(可以这么说),因此将使用一些堆栈。 问题是,解释器的C栈迟早会出现栈溢出。 这正是greenlet扩展所针对的目的,它旨在将堆栈块来回移动到堆中以避免此问题。
如你所知,greenlet,spawn,switch和return有三个基本事件,所以让我们依次看看这些事件:
A)一个产卵
新产生的greenlet与堆栈中的自己的基地址(我们当前所在的地方)相关联。 除此之外,没有特别的事情发生。 新生成的greenlet的python代码以正常方式使用堆,解释器像往常一样继续使用C堆栈。
B)开关
当从greenlet切换到greenlet时,C堆栈的相关部分(从switchng greenlet的基地址开始)被复制到堆中。 复制的C堆栈区域被释放,并且切换的greenlet的解释器先前保存的堆栈数据从堆复制到新释放的C堆栈区域。 切换后的greenlet的python代码以正常方式继续使用堆。 当然,扩展代码会跟踪所有这些(堆部分转到哪个greenlet等等)。
C)回报
堆栈未被触及,返回的greenlet的堆区被python垃圾收集器释放。
基本上就是这样,更多的细节和解释可以在(http://www.stackless.com/pipermail/stackless-dev/2004-March/000022.html)找到,或者通过阅读Alex在答案中指出的代码。
如果得到并学习greenlet的源代码,你会在greenlet.c
的顶部greenlet.c
一个很长的注释,它从第16行开始,具有以下概要...:
PyGreenlet是一系列C堆栈地址,必须以这样一种方式进行保存和恢复:当我们切换到堆栈时,堆栈的全部范围都包含有效数据。
并继续到第82行,准确地总结你在问什么。 你有没有研究这些行(以及以下1000+以上的实施; - )...? 我没有办法进一步压缩这66条线,但仍然有意义,在复制和粘贴这些内容方面没有任何附加价值。
基本上,你会发现没有真正的“挂钩”可言(C级堆栈在“解释者的鼻子下面”来回切换,可以这么说),除了多线程代码中与线程状态的微妙交互,并且从堆栈中保存和恢复greenlet的状态是基于memcpy
调用,以及一些调用Python内存管理器来分配/重新分配和释放来自或返回堆栈的空间。 227-295行中的三个函数处理繁琐的工作,并且它们被包装在298-310的一对C宏中,“为了简化维护”,正如评论中所说的那样。
其他C扩展可以与greenlet扩展交互的接口在956-1045行实现,并通过此处记录的“CObject API”(当然是通过greenlet.h
) greenlet.h
。