绿叶菜是怎么工作的?

2024-09-26 18:16:26 发布

您现在位置: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。)


Tags: 源代码堆栈系统编程事件cpython线程greenlet
2条回答

如果获取并研究greenlet的sources,您将在greenlet.c的顶部看到一条从第16行开始的长注释,其摘要如下:

A PyGreenlet is a range of C stack addresses that must be saved and restored in such a way that the full range of the stack contains valid data when we switch to it.

接着是第82行,准确地总结了你所问的问题。你有没有研究过这些台词(以及随后的1000多条执行台词;-)。。。?我看不出有什么办法可以在有意义的情况下进一步压缩这66行,在这里复制粘贴也没有任何附加值。

基本上,您将看到除了多线程代码中与线程状态的微妙交互之外,没有真正的“挂起”(可以这么说,C级堆栈是在“解释器的鼻子下面”来回切换的),并且从/到堆栈的greenlet's状态的保存和恢复是基于memcpy调用和一些对用于分配/重新分配和释放来自或返回堆栈的空间的Python内存管理器。第227-295行中的三个函数处理繁重的工作,它们被封装在298-310处的一对C宏中,“以简化维护”,正如上面的注释所说。

其他C扩展可以通过它与greenlet扩展交互的接口在第956-1045行实现,并通过“CObject API”(当然是通过greenlet.h)文档化的here公开。

当一个python程序运行时,实际上有两段代码在引擎盖下运行。

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

现在在一个典型的微线程应用程序中,您将有成千上万甚至数百万个微线程(greenlet)在各地切换。每个开关本质上相当于一个具有延迟返回(可以说)的函数调用,因此将使用一个堆栈位。问题是,解释器的C堆栈迟早会遇到堆栈溢出。这正是greenlet扩展的目标,它旨在将堆栈的各个部分来回移动到堆中,以避免出现此问题。

如你所知,有三个基本事件是greenlet、一个spawn、一个switch和一个return,所以让我们依次来看它们:

A)繁殖

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

B)开关

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

C)回报

堆栈未被触及,返回的greenlet的堆区域由python垃圾收集器释放。

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

相关问题 更多 >

    热门问题