【发布时间】:2010-10-13 15:45:53
【问题描述】:
Python 的强项之一是易于编写 C 和 C++ 扩展以加速代码的处理器密集型部分。这些扩展可以避免 Global Interpreter Lock 还是它们也受到 GIL 的限制?如果不是,那么这个“易于扩展”比我之前意识到的更具有杀手锏。我怀疑答案不是简单的是或否,但我不确定,所以我在 StackOverflow 上问这个问题。
【问题讨论】:
标签: c++ python c multithreading
Python 的强项之一是易于编写 C 和 C++ 扩展以加速代码的处理器密集型部分。这些扩展可以避免 Global Interpreter Lock 还是它们也受到 GIL 的限制?如果不是,那么这个“易于扩展”比我之前意识到的更具有杀手锏。我怀疑答案不是简单的是或否,但我不确定,所以我在 StackOverflow 上问这个问题。
【问题讨论】:
标签: c++ python c multithreading
是的,对 C 扩展的调用(从 Python 调用的 C 例程)仍受 GIL 约束。
但是,您可以手动在 C 扩展中释放 GIL,只要在将控制权返回给 Python VM 之前小心地重新声明它即可。
有关信息,请查看 Py_BEGIN_ALLOW_THREADS 和 Py_END_ALLOW_THREADS 宏:http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock
【讨论】:
Python 的 C/C++ 扩展不受 GIL 约束。但是,您确实需要知道自己在做什么。 From http://docs.python.org/c-api/init.html:
全局解释器锁用于保护指向当前线程状态的指针。释放锁并保存线程状态时,必须在释放锁之前检索当前线程状态指针(因为另一个线程可以立即获取锁并将自己的线程状态存储在全局变量中)。反之,在获取锁和恢复线程状态时,必须先获取锁,然后再存储线程状态指针。
为什么我要讲这么多细节?因为当从 C 语言创建线程时,它们没有全局解释器锁,也没有线程状态数据结构。这样的线程必须通过首先创建线程状态数据结构,然后获取锁,最后存储它们的线程状态指针来引导自己存在,然后才能开始使用 Python/C API。完成后,他们应该重置线程状态指针,释放锁,最后释放他们的线程状态数据结构。
【讨论】:
看看 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() 进行初始调用(例如在您的模块初始化函数中)