GIL
1 2 3
| 全局解释器锁(Global Interpreter Lock) 当CPython创建变量时,它会分配内存然后计算对 该变量的引用数量,大家通常称之为“引用计数”。 如果引用计数变为0,则从系统中释放该内存。引用 计数变量时需要保护竞争条件,多个线程同时增加 或减少变量引用计数时,可能导致内存泄漏或者错 误的内存释放。 CPython引入了GIL,线程在执行代码时,必须首先 获得解释器的使用权,虽然保证了数据安全,也意 味着单进程下Python多线程的性能没有那么好。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| [ceval_gil.h] static _Py_atomic_int gil_locked = {-1}; static _Py_atomic_address gil_last_holder = {0}; static COND_T gil_cond; static MUTEX_T gil_mutex;
static void create_gil(void) { MUTEX_INIT(gil_mutex); MUTEX_INIT(switch_mutex); COND_INIT(gil_cond); COND_INIT(switch_cond); _Py_atomic_store_relaxed(&gil_last_holder, 0); _Py_ANNOTATE_RWLOCK_CREATE(&gil_locked); _Py_atomic_store_explicit(&gil_locked, 0, _Py_memory_order_release); }
[ceval_gil.h] static void take_gil(PyThreadState *tstate) { int err; err = errno; MUTEX_LOCK(gil_mutex); if (!_Py_atomic_load_relaxed(&gil_locked)) goto _ready;
while (_Py_atomic_load_relaxed(&gil_locked)) { int timed_out = 0; COND_TIMED_WAIT(gil_cond, gil_mutex, INTERVAL, timed_out); ... } _ready: ... _Py_atomic_store_relaxed(&gil_locked, 1); ... }
|
线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| [Modules/_threadmodule.c] static PyMethodDef thread_methods[] = { {"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread, METH_VARARGS, start_new_doc}, {"start_new", (PyCFunction)thread_PyThread_start_new_thread, METH_VARARGS, start_new_doc}, {"allocate_lock", (PyCFunction)thread_PyThread_allocate_lock, METH_NOARGS, allocate_doc}, {"allocate", (PyCFunction)thread_PyThread_allocate_lock, METH_NOARGS, allocate_doc}, ... {NULL, NULL} /* sentinel */ };
[cveal.c] static PyThread_type_lock pending_lock = 0; /* for pending calls */ static unsigned long main_thread = 0; /* Request for dropping the GIL */ static _Py_atomic_int gil_drop_request = {0};
void PyEval_InitThreads(void) { if (gil_created()) // 如果初始化过了 return; create_gil(); take_gil(PyThreadState_GET()); main_thread = PyThread_get_thread_ident(); if (!pending_lock) pending_lock = PyThread_allocate_lock(); }
|
从GIL到字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| [pystate.c] PyThreadState * PyThreadState_New(PyInterpreterState *interp) { return new_threadstate(interp, 1); }
static PyThreadState * new_threadstate(PyInterpreterState *interp, int init) { PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));
if (_PyThreadState_GetFrame == NULL) // 设置获得线程中函数调用栈的操作 _PyThreadState_GetFrame = threadstate_getframe;
... if (init) _PyThreadState_Init(tstate); ... return tstate; }
void _PyThreadState_Init(PyThreadState *tstate) { #ifdef WITH_THREAD _PyGILState_NoteThreadState(tstate); #endif }
|
子线程在创建了自身的线程状态对象后, 通过 _PyGILState_NoteThreadState 将这个线程对象设置到 autoTLSkey 中.
有意思的是, 这时候当前活动的python线程不一定是获得GIL的线程. 这是因为主线程和子线程都是操作系统原生线程, 所以操作系统可能在主线程和子线程之间切换. 这里要区分的一点是, 系统级别的线程调度与GIL无关. 而python级的线程调度才与GIL有关, 前面也说了, python自己模拟一套线程的调度方式. 所以操作系统级的并不一定意味着GIL的得手, 当所有的线程都完成了初始化的动作之后, 操作系统的线程调度和python的线程调度才会同一. 那时, python的线程调度会强制当前活动线程释放GIL, 而这一操作会触发Event内核对象, 这个触发进而触发操作系统进行线程调度. 这个GIL其实是起到了从python级线程到操作系统级线程调度的桥梁作用.
真正争夺GIL的是在 t_bootstrap 中, 透过 PyEval_AcquireThread 获得GIL的话语权. 到了这一步, 子线程将自己挂起, 操作系统的线程调度不能唤起它, 只有等待python的线程调度强制主线程放弃GIL后, 子线程才回被唤醒. 而子线程被唤醒后, 主线程又将陷入等待中.
当子线程被python的线程调度唤醒后, 做的第一件事就是通过 PyThreadState_Swap(tstate) 设置python维护当前线程状态对象, 如操作系统的进程上下文环境恢复一样.