python协程的实现(greenlet源码分析)
基本上读完了greenlet的源代码,代码不多,就2000行C语言的代码,其中有一部分栈寄存器的修改的代码是由汇编实现的。。。
一句话来说明greenlet的实现原理:通过栈的复制切换来实现不同协程之间的切换。。。
那么接下里来具体的来看看greenlet的代码到底是怎么实现的。。。
好了,先来看看greenlet对象对应的C语言结构体:
/** States: stack_stop == NULL && stack_start == NULL: did not start yet stack_stop != NULL && stack_start == NULL: already finished stack_stop != NULL && stack_start != NULL: active **/ //greenlet对象最终对应的数据的C结构体,这里可以理解为python对象的属性 typedef struct _greenlet { PyObject_HEAD char* stack_start; //栈的顶部 将这里弄成null,标示已经结束了 char* stack_stop; //栈的底部 char* stack_copy; //栈保存到的内存地址 intptr_t stack_saved; //栈保存在外面的大小 struct _greenlet* stack_prev; //栈之间的上下层关系 struct _greenlet* parent; //父对象 PyObject* run_info; //其实也就是run对象 struct _frame* top_frame; //这里可以理解为主要是控制python程序计数器 int recursion_depth; //栈深度 PyObject* weakreflist; PyObject* exc_type; PyObject* exc_value; PyObject* exc_traceback; PyObject* dict; } PyGreenlet;
这里stack_start,stack_stop就是用于分别指向当前这个协程的栈的顶部与栈的底部,而且可以通过这几个标志位的值来判断当前这个greenlet对象的状态。。。
另外这里可以看到parent指针,用这个来构成greenlet对象的层次关系。。。
接下来来看看与python对象之间对应关系:
//这个是定义的暴露给python的greenlet对象,它以greenlet.h里面定义的_greenlet位基础,然后指定了方法,创建方法等 PyTypeObject PyGreenlet_Type = { PyVarObject_HEAD_INIT(NULL, 0) "greenlet.greenlet", /* tp_name */ sizeof(PyGreenlet), /* tp_basicsize */ //指定对象的大小,其实也就是结构体的大小 0, /* tp_itemsize */ /* methods */ (destructor)green_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ &green_as_number, /* tp_as _number*/ 0, /* tp_as _sequence*/ 0, /* tp_as _mapping*/ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | GREENLET_GC_FLAGS, /* tp_flags */ "greenlet(run=None, parent=None) -> greenlet\n\n" "Creates a new greenlet object (without running it).\n\n" " - *run* -- The callable to invoke.\n" " - *parent* -- The parent greenlet. The default is the current " "greenlet.", /* tp_doc */ (traverseproc)GREENLET_tp_traverse, /* tp_traverse */ (inquiry)GREENLET_tp_clear, /* tp_clear */ 0, /* tp_richcompare */ offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ green_methods, /* tp_methods */ //对象方法的定义 0, /* tp_members */ green_getsets, /* tp_getset */ //属相方法 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ offsetof(PyGreenlet, dict), /* tp_dictoffset */ //这个对象的dict的偏移 (initproc)green_init, /* tp_init */ //初始化 这里主要是设置run以及parent GREENLET_tp_alloc, /* tp_alloc */ //内存分配,其实也就是分配sizeof(PyGreenlet)的大小 green_new, /* tp_new */ //构造函数 这里会将parent默认的设置为全局的greenlet GREENLET_tp_free, /* tp_free */ //释放 (inquiry)GREENLET_tp_is_gc, /* tp_is_gc */ //gc };
这个里面构造以及初始化函数都提供了,那么先来看看构造函数吧,当在python中执行环境中创建greenlet对象的时候,将会先调用green_new方法,接着调用green_init方法。。。
//创建一个greenlet对象,这个可以理解为greenlet的构造函数 //这里会先将parent默认指向当前环境所属的greenlet对象 //如果在greenlet对象的构造函数中带有parent对象,那么待会在init中会设置 static PyObject* green_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject* o; if (!STATE_OK) return NULL; //先用基本对象来构造 o = PyBaseObject_Type.tp_new(type, ts_empty_tuple, ts_empty_dict); if (o != NULL) { //这里默认将parent设置为ts_current,也就是在哪一个greenlet环境里面创建的,那么新创建的greenlet的parent就是所属的环境greenlet Py_INCREF(ts_current); ((PyGreenlet*) o)->parent = ts_current; } return o; }
这个代码很好理解吧,先构造父类,接着设置当前greenlet的默认parent,这里可以看到将parent默认的设置为当前环境所属的greenlet对象,也就是说假如我们在A中创建另外一个greenlet B,而且构造函数中没有传递parent参数,这个并没有关系,因为默认就已经将B的parent设置为A了...
好了,接下来来看看初始化函数:
//初始化greenlet对象,这个可以理解为在构造之后,将会调用这个方法来进行初始化,主要是检查设置run,以及设置parent //这里可能没有parent参数,不过也没有关系,因为在构造的时候就先设置为创建环境的greenlet static int green_init(PyGreenlet *self, PyObject *args, PyObject *kwargs) { PyObject *run = NULL; PyObject* nparent = NULL; //看当前这两个东西是否存在 static char *kwlist[] = {"run", "parent", 0}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO:green", kwlist, &run, &nparent)) return -1; if (run != NULL) { ///这里run对象是必须有的 //设置run对象 if (green_setrun(self, run, NULL)) return -1; } //这里在初始化的时候可以没有parent if (nparent != NULL && nparent != Py_None) //如果没有指定parent对象,那么将跟对象设置,在这个里面将会进行parent链是否存在环的情况进行判断 return green_setparent(self, nparent, NULL); return 0; }
这里初始化函数主要是设置构造的时候传递进来的执行函数,也就是run对象,然后就如果有传递parent的话,将会将这个greenlet对象的parent设置为传递进来的greenlet对象。。。。。
接下来来看看greenlet对象对应的一些方法的定义:
//这个就是暴露给greenlet对象的的方法了 static PyMethodDef green_methods[] = { {"switch", (PyCFunction)green_switch, //switch方法的定义 METH_VARARGS | METH_KEYWORDS, green_switch_doc}, {"throw", (PyCFunction)green_throw, METH_VARARGS, green_throw_doc}, {"__getstate__", (PyCFunction)green_getstate, METH_NOARGS, NULL}, {NULL, NULL} /* sentinel */ };
这里就可以看到最为重要的switch方法其实对应的green_switch方法,。。嗯,那么接下来就来对照着如下python代码来分析greenlet吧:
from greenlet import greenlet def test1(name): print name gr2.switch() print "over" def test2(): print "test-2" return "test2-fjsfjs" gr1 = greenlet(test1) gr2 = greenlet(test2) value = gr1.switch("fjsfjs") print value
首先,得要先来看看greenlet模块的初始化:
PyMODINIT_FUNC initgreenlet(void) #endif { PyObject* m = NULL; char** p = NULL; PyObject *c_api_object; static void *_PyGreenlet_API[PyGreenlet_API_pointers]; GREENLET_NOINLINE_INIT(); #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&greenlet_module_def); #else m = Py_InitModule("greenlet", GreenMethods); //初始化greenlet模块,这里其实定义的方法比较的少,getcurrent啥的 #endif if (m == NULL) { INITERROR; } //为模块添加版本信息 if (PyModule_AddStringConstant(m, "__version__", GREENLET_VERSION) < 0) { INITERROR; } #if PY_MAJOR_VERSION >= 3 ts_curkey = PyUnicode_InternFromString("__greenlet_ts_curkey"); ts_delkey = PyUnicode_InternFromString("__greenlet_ts_delkey"); #if GREENLET_USE_TRACING ts_tracekey = PyUnicode_InternFromString("__greenlet_ts_tracekey"); ts_event_switch = PyUnicode_InternFromString("switch"); ts_event_throw = PyUnicode_InternFromString("throw"); #endif #else ts_curkey = PyString_InternFromString("__greenlet_ts_curkey"); ts_delkey = PyString_InternFromString("__greenlet_ts_delkey"); #if GREENLET_USE_TRACING ts_tracekey = PyString_InternFromString("__greenlet_ts_tracekey"); ts_event_switch = PyString_InternFromString("switch"); ts_event_throw = PyString_InternFromString("throw"); #endif #endif if (ts_curkey == NULL || ts_delkey == NULL) { INITERROR; } if (PyType_Ready(&PyGreenlet_Type) < 0) { INITERROR; } PyExc_GreenletError = PyErr_NewException("greenlet.error", NULL, NULL); if (PyExc_GreenletError == NULL) { INITERROR; } #if PY_MAJOR_VERSION >= 3 || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 5) PyExc_GreenletExit = PyErr_NewException("greenlet.GreenletExit", PyExc_BaseException, NULL); #else PyExc_GreenletExit = PyErr_NewException("greenlet.GreenletExit", NULL, NULL); #endif if (PyExc_GreenletExit == NULL) { INITERROR; } ts_empty_tuple = PyTuple_New(0); if (ts_empty_tuple == NULL) { INITERROR; } ts_empty_dict = PyDict_New(); if (ts_empty_dict == NULL) { INITERROR; } //这里可以理解为创建root greenlet对象 ts_current = green_create_main(); //首先将ts_current创建为main greenlet if (ts_current == NULL) { INITERROR; } Py_INCREF(&PyGreenlet_Type); //为当前的模块添加属性 PyModule_AddObject(m, "greenlet", (PyObject*) &PyGreenlet_Type); //这个是比较重要的,也就是greenlet的构造结构 Py_INCREF(PyExc_GreenletError); PyModule_AddObject(m, "error", PyExc_GreenletError); Py_INCREF(PyExc_GreenletExit); PyModule_AddObject(m, "GreenletExit", PyExc_GreenletExit); PyModule_AddObject(m, "GREENLET_USE_GC", PyBool_FromLong(GREENLET_USE_GC)); PyModule_AddObject(m, "GREENLET_USE_TRACING", PyBool_FromLong(GREENLET_USE_TRACING)); /* also publish module-level data as attributes of the greentype. */ for (p=copy_on_greentype; *p; p++) { PyObject* o = PyObject_GetAttrString(m, *p); if (!o) continue; PyDict_SetItemString(PyGreenlet_Type.tp_dict, *p, o); Py_DECREF(o); } /* * Expose C API */ /* types */ _PyGreenlet_API[PyGreenlet_Type_NUM] = (void *) &PyGreenlet_Type; /* exceptions */ _PyGreenlet_API[PyExc_GreenletError_NUM] = (void *) PyExc_GreenletError; _PyGreenlet_API[PyExc_GreenletExit_NUM] = (void *) PyExc_GreenletExit; /* methods */ _PyGreenlet_API[PyGreenlet_New_NUM] = (void *) PyGreenlet_New; _PyGreenlet_API[PyGreenlet_GetCurrent_NUM] = (void *) PyGreenlet_GetCurrent; _PyGreenlet_API[PyGreenlet_Throw_NUM] = (void *) PyGreenlet_Throw; _PyGreenlet_API[PyGreenlet_Switch_NUM] = (void *) PyGreenlet_Switch; _PyGreenlet_API[PyGreenlet_SetParent_NUM] = (void *) PyGreenlet_SetParent; #ifdef GREENLET_USE_PYCAPSULE c_api_object = PyCapsule_New((void *) _PyGreenlet_API, "greenlet._C_API", NULL); #else c_api_object = PyCObject_FromVoidPtr((void *) _PyGreenlet_API, NULL); #endif //为当前的模块创建C O=API if (c_api_object != NULL) { PyModule_AddObject(m, "_C_API", c_api_object); } #if PY_MAJOR_VERSION >= 3 return m; #endif }
代码挺长的,不过其实最重要的是要做两件事情:
(1)创建默认的最顶层的greenlet对象。。。而且将ts_current这个全局变量指向它
(2)在当前模块中加入greenlet对象的namespace吧。。。(也不知道该咋说)
这里先来看看这个初始化创建的顶层的greenlet对象是怎么创建的吧:
//创建顶层greenlet对象,栈底部为-1,顶部为1 static PyGreenlet* green_create_main(void) { PyGreenlet* gmain; //待会创建的顶层的greenlet的引用 //创建一个字典对象 PyObject* dict = PyThreadState_GetDict(); if (dict == NULL) { if (!PyErr_Occurred()) PyErr_NoMemory(); return NULL; } /* create the main greenlet for this thread */ //为当前线程创建greenlet对象 gmain = (PyGreenlet*) PyType_GenericAlloc(&PyGreenlet_Type, 0); if (gmain == NULL) return NULL; gmain->stack_start = (char*) 1; //栈顶部为1 //因为无符号,所以-1其实是最大的,这里在栈的复制拷贝的时候,就可以保证这里是最原始的,因为栈是由高地址向低地址扩展的 gmain->stack_stop = (char*) -1; //栈底部为-1 因为char*是无符号的,所以可以保证这个是最大的 gmain->run_info = dict; Py_INCREF(dict); return gmain; }
这里其实最终要的就是设置了两个值,首先是stack_stop栈底指针。。设置成了-1,因为char*本身是无符号的,所以其实这里就保证了stack_stop其实设置的时最大值。。。这个很重要。。因为要明白,栈是由高地址向低地址扩展的。。然后就还设置了stack_start栈顶指针。。。
好了,greenlet模块的初始化完成了,那么接下来开始来分析python代码:
gr1 = greenlet(test1)
创建了一个greenlet对象,传入了一个run函数,也就是test1函数,根据前面看到的构造和初始化函数可以知道,当前gr1的parent将会指向最开始初始化建立的greenlet对象。。。
接着看python代码:
gr2 = greenlet(test2)
这里创建了greenlet对象,名字叫gr2,运行函数是test2,嗯,同理,它的parent也是初始化建立的greenlet对象。。。
接着看代码:
value = gr1.switch("fjsfjs")
这里调用gr1的switch方法,那么接下来就开始进入C语言的范围了。。我们来看看它的实现吧:
//greenlet对象的switch方法其实就是对应到这个方法的 //其实最终调用的时g_switch方法 //第一个参数是执行switch的greenlet对象,第二参数是参数数组,第三个是关键字参数 static PyObject* green_switch( PyGreenlet* self, PyObject* args, PyObject* kwargs) { Py_INCREF(args); Py_XINCREF(kwargs); //调用g_switch方法来进行栈的切换 return single_result(g_switch(self, args, kwargs)); }
这里有3个参数,self就是调用switch方法的green let对象了,args是参数列表,kwargs是关键字参数。。。
这里调用了g_switch方法来接下里执行。。
//在python层面中,调用了greenlet的switch方法,其实是最终调用这个来进行执行的 //这里target参数就是执行switch的greenlet对象 //(1)先获取运行信息,参数啥的(2)从切换到的greenlet开始,找到一个可以运行的greenlet对象,通过parent向上遍历 //(3)如果这个greenlet已经是active了,那么直接调用g_switchstack方法,如果这个greenlet还没有启动,那么需要g_initialstub进行初始化 static PyObject * g_switch(PyGreenlet* target, PyObject* args, PyObject* kwargs) { /* _consumes_ a reference to the args tuple and kwargs dict, and return a new tuple reference */ int err = 0; PyObject* run_info; /* check ts_current */ if (!STATE_OK) { //如果状态不对,那么这里需要减少参数的引用计数 Py_XDECREF(args); Py_XDECREF(kwargs); return NULL; } run_info = green_statedict(target); //获取运行信息 if (run_info == NULL || run_info != ts_current->run_info) { //检查一下运行信息,万一是null咋办 Py_XDECREF(args); Py_XDECREF(kwargs); PyErr_SetString(PyExc_GreenletError, run_info ? "cannot switch to a different thread" : "cannot switch to a garbage collected greenlet"); return NULL; } ts_passaround_args = args; //保存switch传进来的参数 ts_passaround_kwargs = kwargs; //保存switch传进来的关键字参数 /* find the real target by ignoring dead greenlets, and if necessary starting a greenlet. */ //找到真正需要指定的greenlet while (target) { //需要通过parent引用向上遍历,只要找到一个可用的就停止 if (PyGreenlet_ACTIVE(target)) { //找到一个可用的greenlet ts_target = target; // 指向运行的greenlet err = g_switchstack(); //切换到这个栈,里面会调用slp_switch方法,用汇编实现 break; } if (!PyGreenlet_STARTED(target)) { //如果都没有启动,那么需要初始化 //这个变量挺有意思的 void* dummymarker; //这里创建了一个新的局部变量,其实是通过它的地址待会来分割新创建的栈,用其来做新栈的底部 ts_target = target; err = g_initialstub(&dummymarker); //对于没有启动的协程,这里需要进行初始化 if (err == 1) { continue; /* retry the switch */ } break; } target = target->parent; } /* For a very short time, immediately after the 'atomic' g_switchstack() call, global variables are in a known state. We need to save everything we need, before it is destroyed by calls into arbitrary Python code. */ //这里处理switch返回参数,这里 //这里可以看出来,其实switch的返回值也是通过ts_passaround_args,ts_passaround_kwargs这两个全局变量来传递的 args = ts_passaround_args; ts_passaround_args = NULL; kwargs = ts_passaround_kwargs; ts_passaround_kwargs = NULL; if (err < 0) { //如果在栈的切换的时候出现了错误 /* Turn switch errors into switch throws */ assert(ts_origin == NULL); Py_CLEAR(kwargs); Py_CLEAR(args); } else { PyGreenlet *origin; #if GREENLET_USE_TRACING PyGreenlet *current; PyObject *tracefunc; #endif origin = ts_origin; ts_origin = NULL; #if GREENLET_USE_TRACING current = ts_current; if ((tracefunc = PyDict_GetItem(current->run_info, ts_tracekey)) != NULL) { Py_INCREF(tracefunc); if (g_calltrace(tracefunc, args ? ts_event_switch : ts_event_throw, origin, current) < 0) { /* Turn trace errors into switch throws */ Py_CLEAR(kwargs); Py_CLEAR(args); } Py_DECREF(tracefunc); } #endif Py_DECREF(origin); } /* We need to figure out what values to pass to the target greenlet based on the arguments that have been passed to greenlet.switch(). If switch() was just passed an arg tuple, then we'll just return that. If only keyword arguments were passed, then we'll pass the keyword argument dict. Otherwise, we'll create a tuple of (args, kwargs) and return both. */ if (kwargs == NULL) //没有关键字参数,那么直接返回参数列表 { return args; } else if (PyDict_Size(kwargs) == 0) { Py_DECREF(kwargs); return args; } else if (PySequence_Length(args) == 0) { Py_DECREF(args); return kwargs; } else //两种参数都有 { PyObject *tuple = PyTuple_New(2); if (tuple == NULL) { Py_DECREF(args); Py_DECREF(kwargs); return NULL; } PyTuple_SET_ITEM(tuple, 0, args); PyTuple_SET_ITEM(tuple, 1, kwargs); return tuple; } }
这里target参数也就是执行了switch方法的green let对象,对应到python代码,也就是我们建立的gr1对象。。
一个while循环。。。
这里需要注意,由于我们的gr1并没有开始运行,所以将会执行如下代码:
//这个变量挺有意思的 void* dummymarker; //这里创建了一个新的局部变量,其实是通过它的地址待会来分割新创建的栈,用其来做新栈的底部 ts_target = target; err = g_initialstub(&dummymarker); //对于没有启动的协程,这里需要进行初始化
先是在当前栈里面创建了一个dummymarker对象,然后将ts_target这个全局变量指向gr1,然后调用g_initialstub方法来初始化,并且将dummymarker的地址也传进去了。。。这个参数很重要,因为它将被设置成gr1的栈底指针。。。
好了,来看看g_initialstub方法:
//对于刚刚创建,都还没有启动的greenlet对象,第一调用switch方法的时候将会调用这里进行初始化 static int GREENLET_NOINLINE(g_initialstub)(void* mark) { int err; PyObject *o, *run; PyObject *exc, *val, *tb; PyObject *run_info; PyGreenlet* self = ts_target; PyObject* args = ts_passaround_args; //获取switch传进来的参数 PyObject* kwargs = ts_passaround_kwargs; //switch传进来的关键字参数 /* save exception in case getattr clears it */ PyErr_Fetch(&exc, &val, &tb); /* self.run is the object to call in the new greenlet */ run = PyObject_GetAttrString((PyObject*) self, "run"); //获取run属性 if (run == NULL) { Py_XDECREF(exc); Py_XDECREF(val); Py_XDECREF(tb); return -1; } /* restore saved exception */ PyErr_Restore(exc, val, tb); /* recheck the state in case getattr caused thread switches */ if (!STATE_OK) { Py_DECREF(run); return -1; } /* recheck run_info in case greenlet reparented anywhere above */ run_info = green_statedict(self); if (run_info == NULL || run_info != ts_current->run_info) { Py_DECREF(run); PyErr_SetString(PyExc_GreenletError, run_info ? "cannot switch to a different thread" : "cannot switch to a garbage collected greenlet"); return -1; } /* by the time we got here another start could happen elsewhere, * that means it should now be a regular switch */ if (PyGreenlet_STARTED(self)) { Py_DECREF(run); ts_passaround_args = args; ts_passaround_kwargs = kwargs; return 1; } /* start the greenlet */ self->stack_start = NULL; //设置栈的顶部为null self->stack_stop = (char*) mark; //设置栈底地址,其实这个地址是从当前运行栈里面新创建了一个变量来获取的 //这里设置栈之间的层次关系 if (ts_current->stack_start == NULL) { //如果当前环境greenlet环境栈不在,那么向上设置一层 /* ts_current is dying */ self->stack_prev = ts_current->stack_prev; } else { self->stack_prev = ts_current; //一般情况下就将栈层次设置为当前环境所属的greenlet } //初始化当前栈的信息 self->top_frame = NULL; self->exc_type = NULL; self->exc_value = NULL; self->exc_traceback = NULL; self->recursion_depth = PyThreadState_GET()->recursion_depth; /* restore arguments in case they are clobbered */ ts_target = self; ts_passaround_args = args; ts_passaround_kwargs = kwargs; /* perform the initial switch */ //栈切换,将当前环境所属的greenlet栈换出去,然后将要执行的greenlet的栈换进来 //这里为啥会返回两次呢? //对于切换到的新的greenlet,因为在执行栈的保存的时候直接提前对于slp_switch函数返回了1 ,接下来就就开始run的执行 //而对于原来的greenlet,它的运行就已经停在了g_switchstack,并没有进行下去了,以后switch这个greenlet的时候将在这里再返回一次 //但是这个时候将会返回0 err = g_switchstack(); /* returns twice! The 1st time with err=1: we are in the new greenlet The 2nd time with err=0: back in the caller's greenlet */ //当返回1的时候,表示是在新创建的greenlet里面,如果是0,那么是在调用环境的greenlet里面 //如果是在新的栈里面,那么需要执行run if (err == 1) { /* in the new greenlet */ PyGreenlet* origin; #if GREENLET_USE_TRACING PyObject* tracefunc; #endif PyObject* result; PyGreenlet* parent; self->stack_start = (char*) 1; /* running */ //这里其实主要是充当标识 /* grab origin while we still can */ origin = ts_origin; //指向切换之前的greenlet ts_origin = NULL; /* now use run_info to store the statedict */ o = self->run_info; self->run_info = green_statedict(self->parent); Py_INCREF(self->run_info); Py_XDECREF(o); #if GREENLET_USE_TRACING if ((tracefunc = PyDict_GetItem(self->run_info, ts_tracekey)) != NULL) { Py_INCREF(tracefunc); if (g_calltrace(tracefunc, args ? ts_event_switch : ts_event_throw, origin, self) < 0) { /* Turn trace errors into switch throws */ Py_CLEAR(kwargs); Py_CLEAR(args); } Py_DECREF(tracefunc); } #endif Py_DECREF(origin); if (args == NULL) { /* pending exception */ result = NULL; } else { /* call g.run(*args, **kwargs) */ //执行run,然后获取返回的参数 result = PyEval_CallObjectWithKeywords( //开始执行当前协程的run run, args, kwargs); Py_DECREF(args); Py_XDECREF(kwargs); } Py_DECREF(run); result = g_handle_exit(result); //这里对run的返回参数进行一下预处理 /* jump back to parent */ //这里实现了跳转到parent的逻辑 //这里可以看出,对于已经运行完了的greenlet对象,它之后是切换到parent对象上继续执行 self->stack_start = NULL; /* dead */ //用这个标记当前greenlet已经结束了,待会在switch的时候就不会保存这个栈的数据到内存了 for (parent = self->parent; parent != NULL; parent = parent->parent) { /* ** 这里要看到这里将run执行完之后返回的result参数传入了switch ** */ result = g_switch(parent, result, NULL); /* Return here means switch to parent failed, * in which case we throw *current* exception * to the next parent in chain. */ assert(result == NULL); } /* We ran out of parents, cannot continue */ PyErr_WriteUnraisable((PyObject *) self); Py_FatalError("greenlets cannot continue"); } /* back in the parent */ if (err < 0) { /* start failed badly, restore greenlet state */ self->stack_start = NULL; self->stack_stop = NULL; self->stack_prev = NULL; } return err; }
这里可以看到,确实是将当前gr1的栈底stack_stop指向了刚刚传进来的地址,基本上上面的注释也说的比较的清楚了。。其实真正的栈的复制与切换是在如下代码:
//这里为啥会返回两次呢? //对于切换到的新的greenlet,因为在执行栈的保存的时候直接提前对于slp_switch函数返回了1 ,接下来就就开始run的执行 //而对于原来的greenlet,它的运行就已经停在了g_switchstack,并没有进行下去了,以后switch这个greenlet的时候将在这里再返回一次 //但是这个时候将会返回0 err = g_switchstack();
嗯,这里将会要返回两次额。。也就是会得到两个err的值。。。为啥要返回两次了。。其实理解到了这个也就理解到了整个greenlet切换的关键了。。。返回1是在我们新的greenlet的执行环境中,也就是gr1,如果返回0,那么就是在调用switch方法所属的greenlet的环境中。。。
好了这里也说不明白为啥会返回两次。。接下来看代码吧:
//将栈转移到真正要执行的地方去 //这里将会调用使用汇编实现的slp_switch的方法,修改栈寄存器,从而将程序的运行引导到新的greenlet里面去 static int g_switchstack(void) { /* Perform a stack switch according to some global variables that must be set before: - ts_current: current greenlet (holds a reference) - ts_target: greenlet to switch to (weak reference) 要转移到的greenlet - ts_passaround_args: NULL if PyErr_Occurred(), else a tuple of args sent to ts_target (holds a reference) - ts_passaround_kwargs: switch kwargs (holds a reference) On return results are passed via global variables as well: - ts_origin: originating greenlet (holds a reference) - ts_current: current greenlet (holds a reference) - ts_passaround_args: NULL if PyErr_Occurred(), else a tuple of args sent to ts_current (holds a reference) - ts_passaround_kwargs: switch kwargs (holds a reference) It is very important that stack switch is 'atomic', i.e. no calls into other Python code allowed (except very few that are safe), because global variables are very fragile. */ int err; //将当前栈的运行信息保存起来,以后运行的时候再恢复 { /* save state */ PyGreenlet* current = ts_current; //获取当前在线的greenlet对象 PyThreadState* tstate = PyThreadState_GET(); //获取当前线程信息 current->recursion_depth = tstate->recursion_depth; //栈深度 //可以理解为当前环境所属的greenlet的执行到这里就终止了,以后恢复也会到这里来 current->top_frame = tstate->frame; //保存当前线程执行栈的顶部帧 ,其实主要是为了处理程序计数器 current->exc_type = tstate->exc_type; //执行类型 current->exc_value = tstate->exc_value; current->exc_traceback = tstate->exc_traceback; } //其实如果是新运行的greenlet,这里没有什么可以换进来的 //这里需要理解的是:假如是A切换到B,那么A的运行将在slp_switch随着栈的切换而停滞,而B的运行将在这个里面开始,当以后重新切换到A的时候,其实也是从slp_switch开始恢复的 err = slp_switch(); //这里进行栈的切换,到这里就可以理解为当前环境greenlet的栈信息就已经切换出去了 ,然后新的greenlet对象的栈信息被换进来了 //返回小于0标示出错了 if (err < 0) { /* error */ PyGreenlet* current = ts_current; current->top_frame = NULL; current->exc_type = NULL; current->exc_value = NULL; current->exc_traceback = NULL; assert(ts_origin == NULL); ts_target = NULL; } else { //转移到ts_target的栈,这里重新设置当前的运行信息 PyGreenlet* target = ts_target; PyGreenlet* origin = ts_current; PyThreadState* tstate = PyThreadState_GET(); tstate->recursion_depth = target->recursion_depth; tstate->frame = target->top_frame; //这里主要是为了设置python的程序计数器 target->top_frame = NULL; tstate->exc_type = target->exc_type; target->exc_type = NULL; tstate->exc_value = target->exc_value; target->exc_value = NULL; tstate->exc_traceback = target->exc_traceback; target->exc_traceback = NULL; assert(ts_origin == NULL); Py_INCREF(target); ts_current = target; //将当前ts_current 设置为switch到的target ts_origin = origin; //将ts_origin指向前面 ts_target = NULL; } return err; }
这里保存了当前环境所属的greenlet的状态信息,然后调用汇编写的slp_switch方法来进行真正的栈切换,接着更改一些全局变量。。。
那么这里的关键就是要理解slp_switch方法了。。
根据不同的系统,会有不同的实现。。因为我的时mac os 64位,所以实现如下:
//因为当前所属的greenlet的执行栈肯定是在栈层次最上面的 //如果这里要切换到的greenlet的栈其实还在下面,那么需要将前面的栈的信息都拷贝到堆空间里面去 //当然这里也存在特殊情况,例如切换到的时新的greenlet,那么其实就不用拷贝 static int slp_switch(void) { int err; void* rbp; void* rbx; unsigned int csr; unsigned short cw; register long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("fstcw %0" : "=m" (cw)); __asm__ volatile ("stmxcsr %0" : "=m" (csr)); //这里先获取以及保存rbp,rbx,rsp三个寄存器的数据 __asm__ volatile ("movq %%rbp, %0" : "=m" (rbp)); __asm__ volatile ("movq %%rbx, %0" : "=m" (rbx)); __asm__ ("movq %%rsp, %0" : "=g" (stackref)); //将栈顶的地址(rsp)保存到stackref { //这里会将栈之间的偏移保存到stsizediff变量 //这里是宏定义,如果要切换到的greenlet并没有运行,那么这里宏定义里面就会提前返回1那么g_initialstub里面就可以开始run的执行了 //这里可以看到其实栈的保存是最终地址保存到slp_switch这一层的 SLP_SAVE_STATE(stackref, stsizediff); // 把栈的信息保存到堆空间里面去 //修改rbp以及rsp寄存器值,用于将当前栈切换到新要执行的greenlet的执行栈 __asm__ volatile ( "addq %0, %%rsp\n" "addq %0, %%rbp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); //对于刚刚运行的greenlet,这里其实没有什么恢复的 __asm__ volatile ("xorq %%rax, %%rax" : "=a" (err)); } // 这里要注意,前后rbp,rbx的值已经改变了,因为栈的数据已经改变了 //因为这里已经恢复了切换到的greenlet的栈的信息,所以这里恢复的其实是切换到的greenlet的栈的寄存器的地址 __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx)); __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp)); __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : REGS_TO_SAVE); return err; }
这里最主要的就是先保存了rbp以及rbx寄存器的值,然后将rsp寄存器的值保存到寄存器变量stackref中,
rbp:当前函数的栈底指针
rsp:当前函数栈的栈顶指针
然后调用SLP_SAVE_STATE 来进行当前栈的保存,这个是一个宏定义,如下:
//第一个参数是当前栈的顶部的地址,第二个变量用于保存栈之间的偏移 //这里如果要切换到的greenlet对象还没有开始,那么返回1 #define SLP_SAVE_STATE(stackref, stsizediff) stackref += STACK_MAGIC; if (slp_save_state((char*)stackref)) return -1; if (!PyGreenlet_ACTIVE(ts_target)) return 1; stsizediff = ts_target->stack_start - (char*)stackref
这里先是调用slp_save_state方法来保存栈,如下:
//保存栈的信息,参数是当前栈顶的地址 //这里保存的原则就是,将要切换到的greenlet的对象的栈更上层的栈都保存起来 static int GREENLET_NOINLINE(slp_save_state)(char* stackref) { /* must free all the C stack up to target_stop */ char* target_stop = ts_target->stack_stop; //获取要切换到的greenlet的栈底部 PyGreenlet* owner = ts_current; //当前环境所属的greenlet assert(owner->stack_saved == 0); if (owner->stack_start == NULL) { //这个表示当前greenlet已经执行完了,那么指向栈层次的上一个greenlet owner = owner->stack_prev; /* not saved if dying */ } else { owner->stack_start = stackref; //设置当前环境所属greenlet的栈的顶部地址 } //这里是要保存所有在当前栈层次上更深的栈的信息,将他们保存起来防止干扰 while (owner->stack_stop < target_stop) { //因为栈是从高地址向低地址部分延伸的 /* ts_current is entierely within the area to free */ if (g_save(owner, owner->stack_stop)) //将这个greenlet的栈的信息保存到堆空间 return -1; /* XXX */ owner = owner->stack_prev; //向上遍历 } //这里确实存在可能等于的情况,例如在mai中创建一个协程T1,那么它的parent也就是root,然后switch进行调度,当执行完了之后,start_start会被设置为null //接下来会被switch到parent,那么在上面,owner将会北被指向prev,其实也就是parent,这里就相等了 if (owner != ts_target) { //如果当前环境的greelet不是要切换到的greenlet,那么保存当前 if (g_save(owner, target_stop)) return -1; /* XXX */ } return 0; }
这里可以代码的大体意思就是将属于要切换到的greenlet gr1的栈 更上面greenlet的栈数据都保存起来。。
而这里,ts_current全局变量其实是最开始初始化建立的root greenlet对象,它的栈的栈底指针值是最大的,所以最终只会执行如下代码:
if (owner != ts_target) { //如果当前环境的greelet不是要切换到的greenlet,那么保存当前 if (g_save(owner, target_stop)) return -1; /* XXX */ }
而且,这里传进来的stackref参数其实是slp_switch方法的栈顶地址,我们来看看当前栈的情况:
再看看g_save方法:
//将这个greenlet的栈的信息保存到堆空间里面去 static int g_save(PyGreenlet* g, char* stop) { /* Save more of g's stack into the heap -- at least up to 'stop' g->stack_stop |________| | | | __ stop . . . . . | | ==> . . |________| _______ | | | | | | | | g->stack_start | | |_______| g->stack_copy */ intptr_t sz1 = g->stack_saved; //计算要保存的栈的大小 intptr_t sz2 = stop - g->stack_start; //栈的大小 assert(g->stack_start != NULL); if (sz2 > sz1) { //标示栈的大小已经改变了 char* c = (char*)PyMem_Realloc(g->stack_copy, sz2); if (!c) { PyErr_NoMemory(); return -1; } memcpy(c+sz1, g->stack_start+sz1, sz2-sz1); g->stack_copy = c; //表示栈的信息保存到了这个地方 g->stack_saved = sz2; //保存了这么大的大小 } return 0; }
可以看到,其实保存的是dummymarker到slp_switch栈顶地址这段空间的值。。嗯,这个很重要。。。
接着因为我们切换到的greenlet gr1 并没有运行,所以直接提前返回1了,那么当前slp_switch将会返回1,然后g_switchstack方法也就返回了1,那么也就是解释了在g_initialstub方法中的第一个返回1是怎么来的。。。
那么当前的执行回到了g_initialstub方法中,因为返回了1,所以可以执行如下代码了:
result = PyEval_CallObjectWithKeywords( //开始执行当前协程的run run, args, kwargs);
这里就是调用新的greenlet 也就是gr1 的run也就是我们python代码中的test1函数。。。
def test1(name): print name gr2.switch() print "over"
用上述同理的方法,来分析gr2.switch(),这里将会切换到gr2的执行,也就是test2函数,在执行之前gr2的栈的内容也将会被保存。。。
def test2(): print "test-2" return "test2-fjsfjs"
当test2执行完了之后,将会执行回到g_initialstub中,执行如下代码:
/* jump back to parent */ //这里实现了跳转到parent的逻辑 //这里可以看出,对于已经运行完了的greenlet对象,它之后是切换到parent对象上继续执行 self->stack_start = NULL; /* dead */ //用这个标记当前greenlet已经结束了,待会在switch的时候就不会保存这个栈的数据到内存了 for (parent = self->parent; parent != NULL; parent = parent->parent) { /* ** 这里要看到这里将run执行完之后返回的result参数传入了switch ** */ result = g_switch(parent, result, NULL); /* Return here means switch to parent failed, * in which case we throw *current* exception * to the next parent in chain. */ assert(result == NULL); }
也就是将切换到parent来运行,这里parent也就是最开始初始化建立的greenlet对象。。。
那么这里在slp_switch方法中就会产生不一样的地方了。。。
//因为当前所属的greenlet的执行栈肯定是在栈层次最上面的 //如果这里要切换到的greenlet的栈其实还在下面,那么需要将前面的栈的信息都拷贝到堆空间里面去 //当然这里也存在特殊情况,例如切换到的时新的greenlet,那么其实就不用拷贝 static int slp_switch(void) { int err; void* rbp; void* rbx; unsigned int csr; unsigned short cw; register long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("fstcw %0" : "=m" (cw)); __asm__ volatile ("stmxcsr %0" : "=m" (csr)); //这里先获取以及保存rbp,rbx,rsp三个寄存器的数据 __asm__ volatile ("movq %%rbp, %0" : "=m" (rbp)); __asm__ volatile ("movq %%rbx, %0" : "=m" (rbx)); __asm__ ("movq %%rsp, %0" : "=g" (stackref)); //将栈顶的地址(rsp)保存到stackref { //这里会将栈之间的偏移保存到stsizediff变量 //这里是宏定义,如果要切换到的greenlet并没有运行,那么这里宏定义里面就会提前返回1那么g_initialstub里面就可以开始run的执行了 //这里可以看到其实栈的保存是最终地址保存到slp_switch这一层的 SLP_SAVE_STATE(stackref, stsizediff); // 把栈的信息保存到堆空间里面去 //修改rbp以及rsp寄存器值,用于将当前栈切换到新要执行的greenlet的执行栈 __asm__ volatile ( "addq %0, %%rsp\n" "addq %0, %%rbp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); //对于刚刚运行的greenlet,这里其实没有什么恢复的 __asm__ volatile ("xorq %%rax, %%rax" : "=a" (err)); } // 这里要注意,前后rbp,rbx的值已经改变了,因为栈的数据已经改变了,虽然代码指令没有变,但是栈已经变了 //因为这里已经恢复了切换到的greenlet的栈的信息,所以这里恢复的其实是切换到的greenlet的栈的寄存器的地址 __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx)); __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp)); __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : REGS_TO_SAVE); return err; }
这里在执行宏定义SLP_SAVE_STATE的时候将不会提前将slp_switch函数返回,而是将会计算得到当前gr2的栈与parent栈的偏移量,存在stsizediff寄存器变量中。。。
然后通过汇编来修改rsp与rbp寄存器的值,达到切换栈的效果。。。
然后SLP_RESTORE_STATE调用这个宏定义将以前保存的parent 的栈数据。。。前面已经说过了,也就是:
也就是说,接下来执行的代码
__asm__ volatile ("xorq %%rax, %%rax" : "=a" (err)); } // 这里要注意,前后rbp,rbx的值已经改变了,因为栈的数据已经改变了,虽然代码指令没有变,但是栈已经变了 //因为这里已经恢复了切换到的greenlet的栈的信息,所以这里恢复的其实是切换到的greenlet的栈的寄存器的地址 __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx)); __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp)); __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : REGS_TO_SAVE); return err;
他们的运行已经在以前parent,也就是初始化建立的greenlet对象的栈里面执行了。。。这里也就解释了为啥在g_initialstub函数中,g_switchstack会返回两次,前面一次会返回1,而这一次会返回0.。。
那么到这里value = gr1.switch("fjsfjs")也就执行完了。。
回到最开的python代码:
执行:print value
到这里greenlet是如何实现栈的复制与切换就说的差不多了。。。。
当然还有一些东西没有说,例如参数的传递啥的。。就不细说了,看代码都能比较容易明白。。。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。