Одной из самых сильных сторон Python является простота написания расширений C и C++ для ускорения выполнения частей кода, интенсивно использующих процессор. Могут ли эти расширения избежать глобальной блокировки интерпретатора или они также ограничены GIL? Если нет, то эта «легкость расширения» является даже более убийственной функцией, чем я думал ранее. Я подозреваю, что ответ не будет простым «да» или «нет», но я не уверен, поэтому я задаю вопрос здесь, в StackOverflow.
Параллелизм: на расширения Python, написанные на C/C++, влияет глобальная блокировка интерпретатора?
Ответы:
Да, вызовы расширений C (подпрограммы C, вызываемые из Python) по-прежнему подпадают под действие GIL.
Однако вы можете вручную освободить GIL внутри вашего расширения C, если вы будете осторожны, чтобы повторно подтвердить его, прежде чем вернуть управление виртуальной машине Python.
Для получения информации взгляните на макросы Py_BEGIN_ALLOW_THREADS
и Py_END_ALLOW_THREADS
: http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock
Расширения C/C++ для Python не связаны GIL. Тем не менее, вам действительно нужно знать, что вы делаете. http://docs.python.org/c-api/init.html:
Глобальная блокировка интерпретатора используется для защиты указателя на текущее состояние потока. При снятии блокировки и сохранении состояния потока указатель текущего состояния потока должен быть извлечен до того, как блокировка будет снята (поскольку другой поток может немедленно получить блокировку и сохранить свое состояние потока в глобальной переменной). И наоборот, при получении блокировки и восстановлении состояния потока блокировка должна быть получена до сохранения указателя состояния потока.
Почему я так подробно рассказываю об этом? Потому что, когда потоки создаются из C, они не имеют глобальной блокировки интерпретатора и не имеют для них структуры данных состояния потока. Такие потоки должны запустить себя, сначала создав структуру данных состояния потока, затем получив блокировку и, наконец, сохранив свой указатель состояния потока, прежде чем они смогут начать использовать API Python/C. По завершении они должны сбросить указатель состояния потока, снять блокировку и, наконец, освободить свою структуру данных состояния потока.
Проверьте Cython, он имеет синтаксис, аналогичный Python, но с несколькими конструкциями, такими как «cdef», быстрыми функциями доступа к numpy и оператором «with nogil» (который делает то, что он говорит).
Если вы пишете расширение на C++, вы можете использовать RAII для простого и разборчивого написания кода, управляющего GIL. Я использую эту пару структур RAII:
namespace py {
namespace gil {
struct release {
PyThreadState* state;
bool active;
release()
:state(PyEval_SaveThread()), active(true)
{}
~release() { if (active) { restore(); } }
void restore() {
PyEval_RestoreThread(state);
active = false;
}
};
struct ensure {
PyGILState_STATE* state;
bool active;
ensure()
:state(PyGILState_Ensure()), active(true)
{}
~ensure() { if (active) { restore(); } }
void restore() {
PyGILState_Release(state);
active = false;
}
};
}
}
… позволяет переключать GIL для данного блока (семантический способ, который может показаться смутно знакомым любому поклоннику контекстного менеджера Pythonista):
PyObject* YourPythonExtensionFunction(PyObject* self, PyObject* args) {
Py_SomeCAPICall(…); /// generally, if it starts with Py* it needs the GIL
Py_SomeOtherCall(…); /// ... there are exceptions, see the docs
{
py::gil::release nogil;
std::cout << "Faster and less block-y I/O" << std::endl
<< "can run inside this block -" << std::endl
<< "unimpeded by the GIL";
}
Py_EvenMoreAPICallsForWhichTheGILMustBeInPlace(…);
}
… Действительно, лично я также нахожу простоту расширения Python и уровень контроля над внутренними структурами и состоянием убийственной функцией.
PyEval_InitThreads()
(например, в функции инициализации вашего модуля), прежде чем пытаться выполнить какое-либо ручное управление GIL. 08.04.2016