PEP: 311 Title: Simplified Global Interpreter Lock Acquisition for
Extensions Version: $Revision$ Last-Modified: $Date$ Author: Mark
Hammond <mhammond@skippinet.com.au> Status: Final Type: Standards Track
Content-Type: text/x-rst Created: 05-Feb-2003 Post-History: 05-Feb-2003
14-Feb-2003 19-Apr-2003

Abstract
========

This PEP proposes a simplified API for access to the Global Interpreter
Lock (GIL) for Python extension modules. Specifically, it provides a
solution for authors of complex multi-threaded extensions, where the
current state of Python (i.e., the state of the GIL is unknown.

This PEP proposes a new API, for platforms built with threading support,
to manage the Python thread state. An implementation strategy is
proposed, along with an initial, platform independent implementation.

Rationale
=========

The current Python interpreter state API is suitable for simple,
single-threaded extensions, but quickly becomes incredibly complex for
non-trivial, multi-threaded extensions.

Currently Python provides two mechanisms for dealing with the GIL:

-   `Py_BEGIN_ALLOW_THREADS` and `Py_END_ALLOW_THREADS` macros. These
    macros are provided primarily to allow a simple Python extension
    that already owns the GIL to temporarily release it while making an
    "external" (ie, non-Python), generally expensive, call. Any existing
    Python threads that are blocked waiting for the GIL are then free to
    run. While this is fine for extensions making calls from Python into
    the outside world, it is no help for extensions that need to make
    calls into Python when the thread state is unknown.

-   `PyThreadState` and `PyInterpreterState` APIs. These API functions
    allow an extension/embedded application to acquire the GIL, but
    suffer from a serious boot-strapping problem - they require you to
    know the state of the Python interpreter and of the GIL before they
    can be used. One particular problem is for extension authors that
    need to deal with threads never before seen by Python, but need to
    call Python from this thread. It is very difficult, delicate and
    error prone to author an extension where these "new" threads always
    know the exact state of the GIL, and therefore can reliably interact
    with this API.

For these reasons, the question of how such extensions should interact
with Python is quickly becoming a FAQ. The main impetus for this PEP, a
thread on python-dev \[1\]\_, immediately identified the following
projects with this exact issue:

-   The win32all extensions
-   Boost
-   ctypes
-   Python-GTK bindings
-   Uno
-   PyObjC
-   Mac toolbox
-   PyXPCOM

Currently, there is no reasonable, portable solution to this problem,
forcing each extension author to implement their own hand-rolled
version. Further, the problem is complex, meaning many implementations
are likely to be incorrect, leading to a variety of problems that will
often manifest simply as "Python has hung".

While the biggest problem in the existing thread-state API is the lack
of the ability to query the current state of the lock, it is felt that a
more complete, simplified solution should be offered to extension
authors. Such a solution should encourage authors to provide error-free,
complex extension modules that take full advantage of Python's threading
mechanisms.

Limitations and Exclusions
==========================

This proposal identifies a solution for extension authors with complex
multi-threaded requirements, but that only require a single
"PyInterpreterState". There is no attempt to cater for extensions that
require multiple interpreter states. At the time of writing, no
extension has been identified that requires multiple
PyInterpreterStates, and indeed it is not clear if that facility works
correctly in Python itself.

This API will not perform automatic initialization of Python, or
initialize Python for multi-threaded operation. Extension authors must
continue to call `Py_Initialize()`, and for multi-threaded applications,
`PyEval_InitThreads()`. The reason for this is that the first thread to
call `PyEval_InitThreads()` is nominated as the "main thread" by Python,
and so forcing the extension author to specify the main thread (by
forcing her to make this first call) removes ambiguity. As
`Py_Initialize()` must be called before `PyEval_InitThreads()`, and as
both of these functions currently support being called multiple times,
the burden this places on extension authors is considered reasonable.

It is intended that this API be all that is necessary to acquire the
Python GIL. Apart from the existing, standard `Py_BEGIN_ALLOW_THREADS`
and `Py_END_ALLOW_THREADS` macros, it is assumed that no additional
thread state API functions will be used by the extension. Extensions
with such complicated requirements are free to continue to use the
existing thread state API.

Proposal
========

This proposal recommends a new API be added to Python to simplify the
management of the GIL. This API will be available on all platforms built
with `WITH_THREAD` defined.

The intent is that assuming Python has correctly been initialized, an
extension author be able to use a small, well-defined "prologue dance",
at any time and on any thread, which will ensure Python is ready to be
used on that thread. After the extension has finished with Python, it
must also perform an "epilogue dance" to release any resources
previously acquired. Ideally, these dances can be expressed in a single
line.

Specifically, the following new APIs are proposed::

/\* Ensure that the current thread is ready to call the Python C API,
regardless of the current state of Python, or of its thread lock. This
may be called as many times as desired by a thread so long as each call
is matched with a call to PyGILState\_Release(). In general, other
thread-state APIs may be used between \_Ensure() and \_Release() calls,
so long as the thread-state is restored to its previous state before the
Release(). For example, normal use of the Py\_BEGIN\_ALLOW\_THREADS/
Py\_END\_ALLOW\_THREADS macros are acceptable.

      The return value is an opaque "handle" to the thread state when
      PyGILState_Acquire() was called, and must be passed to
      PyGILState_Release() to ensure Python is left in the same state. Even
      though recursive calls are allowed, these handles can *not* be
      shared - each unique call to PyGILState_Ensure must save the handle
      for its call to PyGILState_Release.

      When the function returns, the current thread will hold the GIL.

      Failure is a fatal error.

\*/ PyAPI\_FUNC(PyGILState\_STATE) PyGILState\_Ensure(void);

/\* Release any resources previously acquired. After this call, Python's
state will be the same as it was prior to the corresponding
PyGILState\_Acquire call (but generally this state will be unknown to
the caller, hence the use of the GILState API.)

      Every call to PyGILState_Ensure must be matched by a call to
      PyGILState_Release on the same thread.

\*/ PyAPI\_FUNC(void) PyGILState\_Release(PyGILState\_STATE);

Common usage will be::

void SomeCFunction(void) { /\* ensure we hold the lock */
PyGILState\_STATE state = PyGILState\_Ensure(); /* Use the Python API */
... /* Restore the state of Python \*/ PyGILState\_Release(state); }

Design and Implementation
=========================

The general operation of `PyGILState_Ensure()` will be:

-   assert Python is initialized.

-   Get a `PyThreadState` for the current thread, creating and saving if
    necessary.

-   remember the current state of the lock (owned/not owned)

-   If the current state does not own the GIL, acquire it.

-   Increment a counter for how many calls to `PyGILState_Ensure` have
    been made on the current thread.

-   return

The general operation of `PyGILState_Release()` will be:

-   assert our thread currently holds the lock.

-   If old state indicates lock was previously unlocked, release GIL.

-   Decrement the `PyGILState_Ensure` counter for the thread.

-   If counter == 0:

    -   release and delete the `PyThreadState`.

    -   forget the `ThreadState` as being owned by the thread.

-   return

It is assumed that it is an error if two discrete `PyThreadStates` are
used for a single thread. Comments in `pystate.h` ("State unique per
thread") support this view, although it is never directly stated. Thus,
this will require some implementation of Thread Local Storage.
Fortunately, a platform independent implementation of Thread Local
Storage already exists in the Python source tree, in the SGI threading
port. This code will be integrated into the platform independent Python
core, but in such a way that platforms can provide a more optimal
implementation if desired.

Implementation
==============

An implementation of this proposal can be found at
https://bugs.python.org/issue684256

References
==========

.. \[1\] David Abrahams, Extension modules, Threading, and the GIL
https://mail.python.org/pipermail/python-dev/2002-December/031424.html

Copyright
=========

This document has been placed in the public domain.

.. Local Variables: mode: indented-text indent-tabs-mode: nil
sentence-end-double-space: t fill-column: 70 End:
