Что такое GIL в Python?Кажется, один из золотых вопросов для всех питонистов на собеседованиях.
Обычно, на встречный вопрос "а что конкретно в питоне является GIL?" не может ответить ни один спрашивающий.
Сегодня мы закроем данный пробел в знаниях питонистов.
Global Interpreter Lock не позволяет работать более чем одному треду работать с Python API за раз. Его можно отключить через
--disable-gil
в 3.13+, но сегодня мы про такое не будем.
Обратите внимание на ключевую фразу "c Python API". С системными треды могут и должны работать в режиме настоящей параллельности, без GIL. Что и позволяет получить ускорение при использовании
threading
, когда C код поддерживает такой способ.
Знакомьтесь – вот структура GIL
_gil_runtime_state
и поведение в
ceval_gil.c
.
Как можно отпустить GIL?На уровне C есть макросы: Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS, которые отпускают GIL в нужных местах. Пример из модуля mmap:
Py_BEGIN_ALLOW_THREADS
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);
Py_END_ALLOW_THREADS
Или time.sleep, который тоже дает работать другим тредам, пока ждет.
Что происходит, когда мы используем данный макрос? Они разворачиваются в:
{
PyThreadState *_save;
_save = PyEval_SaveThread();
// your code here
PyEval_RestoreThread(_save);
}
PyThreadState
является текущим состоянием треда в CPython. Внутри хранится много контекста. Нас особо сильно интересует часть с полями про GIL:
struct PyThreadState {
struct {
unsigned int initialized:1;
/* Has been bound to an OS thread. */
unsigned int bound:1;
/* Has been unbound from its OS thread. */
unsigned int unbound:1;
/* Has been bound aa current for the GILState API. */
unsigned int bound_gilstate:1;
/* Currently in use (maybe holds the GIL). */
unsigned int active:1;
/* Currently holds the GIL. */
unsigned int holds_gil:1;
} _status;
// Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).
int state;
// ...
}
Когда вызывается PyEval_SaveThread и GIL отпускается, то на самом деле мы просто помечаем текущий PyThreadState как:
tstate->_status.active = 0;
tstate->_status.unbound = 1;
tstate->_status.holds_gil = 0;
tstate->state = detached_state;
И вызываем _PyEval_ReleaseLock, который уже правильно изменит
_gil_runtime_state
.
Как итог – текущий стейт теряет возможность вызывать какие-либо Python АПИ. Даже, например
Py_DECREF
, и в тредах есть свой refcount, который работает локально, чтобы можно было его вызывать без GIL.
Как треды берут GIL?Смотрим на thread_run из
_threadmodule.c
.
_PyThreadState_Bind(tstate);
PyEval_AcquireThread(tstate);
_Py_atomic_add_ssize(&tstate->interp->threads.count, 1);
Там используется PyEval_AcquireThread, который берет GIL в конкретном треде для работы с Python API.
И дальше – отпускаем.
В следующих сериях поговорим про переключение тредов, ParkingLot API, Mutex'ы и прочее.
Обсуждение: сталкивались ли вы на собесах с вопросами про GIL? Стало ли теперь понятнее?
| Поддержать | YouTube | GitHub | Чат |