¿Cómo funcionan los greenlets?

¿Cómo se implementan los greenlets ? Python usa la stack C para el intérprete y asigna montones de ttwigs de stack de Python, pero más allá de eso, ¿cómo asigna / intercambia stacks, cómo se enlaza con el intérprete y los mecanismos de llamada de función, y cómo interactúa esto con las extensiones C? (Cualquier peculiaridades)?

Hay algunos comentarios en la parte superior de greenlet.c en la fuente, pero son un poco opacos. FWIW Vengo desde la perspectiva de alguien que no está familiarizado con las partes internas de CPython pero que está muy familiarizado con la progtwigción de sistemas de bajo nivel, C, hilos, eventos, líneas paralelas / cooperativas, progtwigción del kernel, etc.

(Algunos puntos de datos: no usan ucontext.h y hacen 2x memcpy, alloc y free en cada cambio de contexto ).

Cuando se ejecuta un progtwig de Python, básicamente tiene dos piezas de código que se ejecutan bajo el capó.

Primero, el código C del intérprete CPython se ejecuta y utiliza la stack C estándar para guardar sus cuadros de stack internos. En segundo lugar, el código de bytes real interpretado por python que no utiliza la stack C, sino que utiliza el montón para guardar sus cuadros de stack. Un greenlet es solo un código estándar de Python y por lo tanto se comporta de manera idéntica.

Ahora, en una aplicación típica de microtornillados, tendrías miles, si no millones, de microtornillos (greenlets) cambiando por todos lados. Cada conmutador es esencialmente equivalente a una llamada de función con un retorno diferido (por así decirlo) y por lo tanto utilizará un poco de stack. El problema es que la stack C del intérprete tarde o temprano llegará a un desbordamiento de stack. Esto es exactamente a lo que apuntaba la extensión greenlet, está diseñado para mover partes de la stack de un lado a otro desde el montón para evitar este problema.

Como saben, hay tres eventos fundamentales con greenlets, un engendro, un cambio y un retorno, así que analicemos esos a su vez:

A) un engendro

El greenlet recién generado está asociado con su propia dirección base en la stack (donde estamos actualmente). Aparte de eso, no pasa nada especial. El código de Python del greenlet recién generado utiliza el montón de una manera normal y el intérprete continúa usando la stack C como de costumbre.

B) un interruptor

Cuando se cambia a un greenlet de un greenlet de conmutación, la parte relevante de la stack C (comenzando desde la dirección base del greenlet de conmutación) se copia al montón. El área de la stack C copiada se libera y los datos de la stack guardada previamente del intérprete de greenlet conmutado se copian del montón al área de la stack C recién liberada. El código de python del greenlet conmutado continúa utilizando el montón de forma normal. Por supuesto, el código de extensión hace un seguimiento de todo esto (qué sección del montón va a qué greenlet y así sucesivamente).

C) una devolución

La stack no ha sido tocada y el recolector de basura Python libera el área del montón del greenlet que regresa.

Básicamente, aquí se encuentran muchos más detalles y explicaciones en ( http://www.stackless.com/pipermail/stackless-dev/2004-March/000022.html ) o simplemente leyendo el código como se indica en la respuesta de Alex. .

Si obtiene y estudia las fonts de greenlet, verá en la parte superior de greenlet.c un comentario largo que comienza en la línea 16 con el siguiente resumen …:

Un PyGreenlet es un rango de direcciones de stack C que deben guardarse y restaurarse de tal manera que el rango completo de la stack contenga datos válidos cuando lo cambiemos.

y continúa en la línea 82, que resume exactamente lo que estás preguntando. ¿Has estudiado estas líneas (y las siguientes 1000+ implementándolas; -) …? No veo una manera de reducir aún más estas 66 líneas mientras aún tiene sentido, ni ningún valor agregado al copiarlas y pegarlas aquí.

Básicamente, verá que no hay un verdadero “enganche” del que hablar (la stack de nivel C se cambia de un lado a otro “por debajo de la nariz del intérprete”, por así decirlo), excepto por las delicadas interacciones con el estado del hilo en el código de múltiples hilos. y el guardar y restaurar el estado de un greenlet desde / hacia la stack se basa en memcpy llamadas memcpy más algunas llamadas al administrador de memoria de Python para asignar / reasignar y liberar espacio proveniente de, o volver a, la stack. Las tres funciones en la línea 227-295 manejan el trabajo duro, y están envueltas en un par de macros C al 298-310 “para simplificar el mantenimiento”, como dice el comentario allí.

La interfaz a través de la cual otras extensiones C pueden interactuar con la extensión greenlet se implementa en las líneas 956-1045, y se expone a través de la “API CObject” (a través de greenlet.h , por supuesto) documentada aquí .