It's recommended to wrap pure C functions that block on IO, or even pure C functions that spend substantial time in CPU, in Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS blocks.
Is there any harm to wrapping pretty much all pure C functions (with pure C arguments- no Python API objects), regardless of whether it blocks on IO or CPU for a long time, with Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS ?
For example, say you had the following function, where some_pure_c_non_io_func spends a small time on a CPU task. Is there any harm in releasing the GIL here?
static PyObject Foo_bar(Foo *self, PyObject *args, PyObject *kwargs)
{
int i = 0;
if (!PyArg_ParseTuple(args, "i", &i)) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
some_pure_c_non_io_func(i);
Py_END_ALLOW_THREADS
Py_RETURN_NONE;
}
I'm not totally sure, but from looking at the code it seems there's no harm. Several popular libraries do this for intensive CPU functions, notably Tensorflow and Pillow
Py_BEGIN_ALLOW_THREADS expands to
PyThreadState *_save;
_save = PyEval_SaveThread();
PyEval_SaveThread saves the current thread state in _save (with `_PyThreadState_Swap) and gives up the GIL
Then Py_END_ALLOW_THREADS expands to
PyEval_RestoreThread(_save);
which does the same thing in reverse (takes GIL and makes _save the current thread state).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With