greenlet是Python的协程,通过各协程之间的切换,实现并发逻辑。
greenlet 数据结构:
typedef struct _greenlet {
PyObject_HEAD
char* stack_start;
char* stack_stop;
char* stack_copy;
intptr_t stack_saved;
struct _greenlet* stack_prev;
struct _greenlet* parent;
PyObject* run_info;
struct _frame* top_frame;
int recursion_depth; PyObject* weakreflist; PyObject* exc_type;
PyObject* exc_value;
PyObject* exc_traceback;
PyObject* dict;
} PyGreenlet;
# 说明:
# stack_start:greenlet在栈上开始的地址;
# stack_stop:greenlet在栈上结束的地址;
# stack_copy:greenlet在堆上开始的地址;
# stack_saved:greenlet保存在堆上的字节数;
# run_info:greenlet绑定的线程的信息,只允许同一线程的greenlet之间切换(switch)
# stack_prev:栈指针,指向前一个greenlet
一些变量说明:
- tstate:当前thread的状态字典,由PyThreadState_GET()得到,其中ts_curkey的值指向当前(current) greenlet
- ts_current:当前执行的greenlet
- ts_target:要切换到的目标greenlet
- ts_origin:切换greenlet后,指向切换前的greenlet
- ts_passaround_args ts_passaround_kwargs:切换greenlet时传递的参数
greenlet堆栈布局:
| ^^^ |
| older data |
| |
stack_stop . |_______________|
. | |
. | greenlet data |
. | in stack |
. * |_______________| . . _____________ stack_copy + stack_saved
. | | | |
. | data | |greenlet data|
. | unrelated | | saved |
. | to | | in heap |
stack_start . | this | . . |_____________| stack_copy
| greenlet |
| |
| newer data |
| vvv |
变化规则:
当切换至其他greenlet时,会有部分数据由栈移至堆中(slp_save_state),避免被新的greenlet覆盖;
当重新切换回时,数据会由堆(如果有数据被保存在堆中)切换(slp_restore_state)回栈中
创建main greenlet:
static PyGreenlet* green_create_main(void)
当tstate[ts_curkey]为空时,说明该线程还没有greenlet,创建main greenlet,其中:
gmain->stack_start = (char*) 1;
gmain->stack_stop = (char*) -1;
表示main greenlet 占据了整个stack
greenlet 切换:
static PyObject * g_switch(PyGreenlet* target, PyObject* args, PyObject* kwargs)
切换greenlet,将执行流切换至target:
-
target没有执行过,stack_start被置为(char*)1,即最低地址,stack_stop被置为当前栈顶地址,表示整个栈被新greenlet占据,和该部分栈有重合的greenlet都要将数据拷贝至堆中;切换后调用target.run方法,调用结束后(target即结束),将执行流切换至target->parent,即当greenlet结束时,控制流会切换至父greenlet
-
target执行过,则将执行流切换至target 切换的过程会将所有stack_stop比target->stack_stop小的greenlet(stack_start~stack_stop)copy到堆中,以免发生覆盖;
如果target有在堆中的部分,则会resotre到栈中,总之,会保证当前执行的greenlet都在栈上;
切换的过程中,旧greenlet的stack_start会被置为当前堆栈地址
切换发生了什么?
当前线程的信息(frame,exc_type,exc_value,exc_trackback)等被替换为target greenlet的对应值;
把堆栈指针指向target greenlet的 stack_start 切换的过程本质是不同执行栈之间的切换,将greenlet在栈与堆之间进行memcpy操作。