Abandoned Wig
Hi, I'm Martin.
I work on the Web Platform at Igalia.

Managing the Python GIL via RAII

06 October 2009
One of the killer features of C++ is RAII. RAII means that the amount of special-case cleanup code in the case of exceptions or early exits is minimized. For more on exactly how this happens, I recommend the Wikipedia article linked above.

This feature became useful to me when making our Python Kroll module work properly with the Python GIL. There were two usage patterns I was interested in for this work.

Letting Python code in other threads run

PyThreadState* threadState = PyEval_SaveThread();
...do some expensive non-Python work here...
PyEval_RestoreThread(threadState);

Taking back the GIL to use the Python API

PyGILState gstate = PyGILState_Ensure();
...use the Python API here...
PyGILState_Release(gstate);

The problem with this approach is that if an uncaught exception escapes from anywhere between the ellipses, the cleanup code (PyEval_RestoreThread or PyGILState_Release) will not run, likely leaving the program in a bad state. It turns out that RAII has a very elegant solution to this problem.

class PyLockGIL
{
PyLockGIL() : gstate(PyGILState_Ensure())
{ }

~PyLockGIL()
{
PyGILState_Release(gstate);
}

PyGILState_STATE gstate;
};

class PyAllowThreads
{
PyAllowThreads() : threadState(PyEval_SaveThread())
{ }

~PyAllowThreads()
{
PyEval_RestoreThread(threadState);
}

PyThreadState* threadState;
};
</code>
The acquisition and release of the GIL is just wrapped in the constructors and destructors of objects that are allocated on the stack. Here is the previous example using these new objects:

{
PyLockGIL lock
...do some expensive non-Python work here...
}

{
PyAllowThreads allow;
...use the Python API here...
}
Notice that braces can be used to fine tune the amount of code with the desired GIL state. The destructor for these objects will be called as soon as they go out of scope.</div>