New PEP 311: Simplified Global Interpreter Lock acquisition for extensions
This commit is contained in:
parent
71edeec4dc
commit
a63e791682
|
@ -0,0 +1,241 @@
|
|||
PEP: 311
|
||||
Title: Simplified Global Interpreter Lock acquisition for extensions
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Mark Hammond <mhammond@skippinet.com.au>
|
||||
Status: Draft
|
||||
Type:
|
||||
Content-Type: text/plain
|
||||
Created: 05-Feb-2003
|
||||
Post-History: 05-Feb-2003 14-Feb-2003
|
||||
|
||||
Open Issues
|
||||
This is where I note comments from people that are yet to be resolved.
|
||||
- JustvR prefers a PyGIL prefix over PyAutoThreadState.
|
||||
- JackJ notes that the "Auto" prefix will look a little silly
|
||||
in a few years, assuming this becomes the standard way of
|
||||
managing the lock. He doesn't really like Just's "GIL", and
|
||||
suggested "PyIntLock"
|
||||
- JackJ prefers "Acquire" over "Ensure", even though the semantics
|
||||
are different than for other "Acquire" functions in the API.
|
||||
Mark still prefers Ensure for exactly this reason.
|
||||
- Mark notes Dutch people must love names, and still remembers
|
||||
"pulling dead cows from the ditch" (but has forgotten the
|
||||
Dutch!) He also hopes Jack remembers the reference <wink>.
|
||||
- Should we provide Py_AUTO_THREAD_STATE macros?
|
||||
- Is my "Limitation" regarding PyEval_InitThreads() OK?
|
||||
|
||||
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
|
||||
specifiy 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 an extension author be able to use a small,
|
||||
well-defined "prologue dance", at any time and on any thread, and
|
||||
assuming Python has correctly been initialized, this dance 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
|
||||
PyAutoThreadState_Release()
|
||||
|
||||
The return value is an opaque "handle" to the thread state when
|
||||
PyAutoThreadState_Ensure() was called, and must be passed to
|
||||
PyAutoThreadState_Release() to ensure Python is left in the same
|
||||
state.
|
||||
|
||||
When the function returns, the current thread will hold the GIL.
|
||||
Thus, the GIL is held by the thread until
|
||||
PyAutoThreadState_Release() is called. (Note that as happens now
|
||||
in Python, calling a Python API function may indeed cause a
|
||||
thread-switch and therefore a GIL ownership change. However,
|
||||
Python guarantees that when the API function returns, the GIL will
|
||||
again be owned by the thread making the call)
|
||||
|
||||
Failure is a fatal error.
|
||||
*/
|
||||
PyAutoThreadState_State PyAutoThreadState_Ensure(void);
|
||||
|
||||
/* Release any resources previously acquired. After this call,
|
||||
Python's state will be the same as it was prior to the
|
||||
corresponding PyAutoThreadState_Ensure call (but generally this
|
||||
state will be unknown to the caller, hence the use of the
|
||||
AutoThreadState API.)
|
||||
|
||||
Every call to PyAutoThreadState_Ensure must be matched by a
|
||||
call to PyAutoThreadState_Release on the same thread.
|
||||
*/
|
||||
void PyAutoThreadState_Release(PyAutoThreadState_State state);
|
||||
|
||||
Common usage will be:
|
||||
|
||||
void SomeCFunction(void)
|
||||
{
|
||||
/* ensure we hold the lock */
|
||||
PyAutoThreadState_State state = PyAutoThreadState_Ensure();
|
||||
/* Use the Python API */
|
||||
...
|
||||
/* Restore the state of Python */
|
||||
PyAutoThreadState_Release(state);
|
||||
}
|
||||
|
||||
Design and Implementation
|
||||
|
||||
The general operation of PyAutoThreadState_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 PyAutoThreadState_Ensure
|
||||
have been made on the current thread.
|
||||
- return
|
||||
|
||||
The general operation of PyAutoThreadState_Release() will be:
|
||||
- assert our thread currently holds the lock.
|
||||
- If old state indicates lock as previously unlocked, release GIL.
|
||||
- Decrement the PyAutoThreadState_Ensure counter for the thread.
|
||||
- If counter == 0:
|
||||
- release 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
|
||||
http://www.python.org/sf/684256
|
||||
|
||||
References
|
||||
|
||||
[1] http://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:
|
Loading…
Reference in New Issue