8000 PEP 805: Safe Parallel Python by markshannon · Pull Request #4579 · python/peps · GitHub
[go: up one dir, main page]

Skip to content
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Apply Hugo's format fixes and spelling corrections
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
  • Loading branch information
markshannon and hugovk authored Sep 9, 2025
commit 49a2e664e2bc87844ecf6d442fb016d0ff8c8245
57 changes: 23 additions & 34 deletions peps/pep-0805.rst
436B
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Author: Mark Shannon <mark@hotpy.org>
Discussions-To: Pending
Status: Draft
Type: Standards Track
Created: 08-Sep-2025
Python-Version: 3.15

Abstract
========
Expand All @@ -23,11 +23,11 @@ Traditionally, CPython has executed in only a single thread of execution at once
This has always been seen as a limitation of Python and there has been a desire for
Python to support parallel execution for many years.

PEP 703, Making the Global Interpreter Lock Optional in CPython, and PEP 554, Multiple Interpreters in the Stdlib, offer ways to support parallelism.
:pep:`703`, Making the Global Interpreter Lock Optional in CPython, and :pep:`554`, Multiple Interpreters in the Stdlib, offer ways to support parallelism.
Multiple interpreters are both safe and support parallelism, but they are difficult to use and sharing objects
between multiple interpreters without copying is impossible.
PEP 703 supports parallel execution and sharing, but is unsafe as it allows race conditions.
Race conditions allow dangerous and hard to find bugs. In the most extreme example, Therac-25 [1]_, a race condition bug resulted in several fatalities. The trouble with race conditions is not that the bugs they introduce are necessarily worse than other bugs,
Race conditions allow dangerous and hard to find bugs. In the most extreme example, `Therac-25 <https://en.wikipedia.org/wiki/Therac-25>`__, a race condition bug resulted in several fatalities. The trouble with race conditions is not that the bugs they introduce are necessarily worse than other bugs,
but that they can be very hard to detect and may easily slip through testing.

Parallelism, without strong support from the language and runtime, is extremely difficult to get right:
Expand Down Expand Up @@ -71,7 +71,7 @@ The PEP also proposes changes to CPython to prevent unsafe execution when mutabl
Finally, the PEP provides a generalization of the GIL to allow incrementally moving away from the GIL.

This PEP is inspired by ideas from OCaml, specifically Data Freedom à la Mode [2]_, and the Pyrona project [3]_.
Many of the necessary technologies, such as biased and deferred reference counting, have been developed for PEP 703.
Many of the necessary technologies, such as biased and deferred reference counting, have been developed for :pep:`703`.

Specification
=============
Expand All @@ -85,7 +85,7 @@ The state can be queried by looking at the ``__shared__`` attribute of an object
An object's ``__shared__`` state can be one of the following:

* Immutable: Cannot be modified, and can be safely shared between threads.
* Local: Only visible to a single thread<sup>*</sup>, and can be freely mutated by that thread.
* Local: Only visible to a single thread\ :sup:`*`, and can be freely mutated by that thread.
* Stop-the-world mutable. Mostly immutable, but can be mutated by acquiring the stop-the-world lock.
* Protected: Object is mutable, and is protected by a mutex.
* Synchronized: A special state for some builtin objects.
Expand All @@ -98,14 +98,14 @@ The ``__shared__`` attribute is read-only.
All classes, modules and functions will be *stop-the-world mutable* initially, but can be made *immutable*.
Views, iterators and other objects that depend on the internal state of other mutable objects will inherit the state of
those objects. For example, a ``listiterator`` of a *local* ``list`` will be *local*, but a ``listiterator`` of a *protected*
``list`` will be *protected*. Views and iterators of *immutable* (including STW mutable) objects will be *local* when created.
``list`` will be *protected*. Views and iterators of *immutable* (including stop-the-world mutable) objects will be *local* when created.

All objects that are not inherently immutable (like tuples or strings) will be created as *local*.
These *local* objects can later be made *immutable* or *protected*.

Dictionaries and lists will support the ability to be made stop-the-world mutable on a per-object basis.
This is to maintain the necessary immutability for performance and mutability for
backwards campatibility for objects like ``sys.modules`` and ``sys.path``.
backwards compatibility for objects like ``sys.modules`` and ``sys.path``.

The stop-the-world lock
-----------------------
Expand All @@ -123,11 +123,11 @@ SuperThread objects
A new class ``SuperThread`` will be added to help port applications using the GIL
and sub-interpreters. All threads sharing a ``SuperThread`` will be serialized,
in the same way as all threads are currently serialized by the GIL.
``SuperThread``\ s offer much the same capabilites as sub-interpreters,
``SuperThread``\ s offer much the same capabilities as sub-interpreters,
but more efficiently and with the ability to share more objects.

There is a many-to-one relationship between threads and ``SuperThread``\ s.
If no super thread is explictly specified when creating a thread,
If no super thread is explicitly specified when creating a thread,
a new super thread will be created specifically for that thread.
The super thread of a thread cannot be changed.

Expand Down Expand Up @@ -215,7 +215,7 @@ Passing mutable values between threads
''''''''''''''''''''''''''''''''''''''

The ``Channel`` class is provided for passing objects from one thread to another.
The ``Channel`` class acts like a ``deque`` but handles tranferring of ownership local objects.
The ``Channel`` class acts like a ``deque`` but handles transferring of ownership local objects.
When passing a *local* object, ``channel.put(obj)`` detaches the object ``obj`` from the current thread.
When passing a *local* object, ``Channel.put()`` will fail, raising a ``ValueError``, if there are any other references to the argument.
``Channel.get()`` returns the object passed but to ``Channel.put()``, making the calling
Expand All @@ -234,7 +234,7 @@ the ``sys`` module is deleted. The main thread's ``SuperThread`` will be the GIL

If the environment variable ``PYTHONGIL=1`` is set, then all new threads will default to
``super_thread = sys.gil``. Otherwise all new threads will default to ``super_thread = None``.
Explictly setting the ``super_thread`` argument when creating a thread will override these defaults.
Explicitly setting the ``super_thread`` argument when creating a thread will override these defaults.

Deadlock detection
------------------
Expand Down Expand Up @@ -265,14 +265,14 @@ If we do that, then all other operations become safe.
| All other operations | Yes | Yes | N/A | Yes | Yes |
+------------------------+-----------+-----------------+-----------------+---------------+----------------+

1. If the mutex held by the thread matches the mutex that protects the object
1. If the mutex held by the thread matches the mutex that protects the object.
2. If supported for that class.
3. The argument to ``__protect__`` must the sole reference to the object.

ABI breakage
------------

This PEP will require a one time ABI breakage, much like PEP 703, as the ``PyObject`` struct will need to be changed.
This PEP will require a one time ABI breakage, much like :pep:`703`, as the ``PyObject`` struct will need to be changed.

Deferred reclamation
--------------------
Expand All @@ -282,7 +282,7 @@ In other words, they may not be reclaimed immediately that their are no more ref

This is because these objects may be referred to from several threads simultaneously, and the overhead
of serializing the reference count operations would be too high.
PEP 703 also does this.
:pep:`703` also does this.

Local objects, visible to only one thread, will still be reclaimed immediately that they are no longer referenced.

Expand Down Expand Up @@ -370,7 +370,7 @@ How to Teach This
=================

In order to run code in parallel, some understanding of the model of execution will
be needed. Writing unsafe code is much harder than under PEP 703, but the
be needed. Writing unsafe code is much harder than under :pep:`703`, but the
new exceptions may surprise users. Extensive documentation will be needed.

Examples
Expand Down Expand Up @@ -444,7 +444,7 @@ inherits the protection from the ``list`` object.
Comparison to PEP 703 (Making the Global Interpreter Lock Optional in CPython)
==============================================================================

This PEP should be thought of as building on PEP 703, rather than competing with or replacing it.
This PEP should be thought of as building on :pep:`703`, rather than competing with or replacing it.
Many of the mechanisms needed to implement this PEP have been developed for PEP 703.

What PEP 703 lacks is well defined semantics for what happens when race conditions are present,
Expand All @@ -454,10 +454,10 @@ PEP 703 attempts to provide single-threaded performance for lists, dictionaries,
and other mutable objects while providing "reasonable" thread safety. Unfortunately,
no formal definition of expected behavior is provided, which leads to issues like these:

* <https://github.com/python/cpython/issues/129619>
* <https://github.com/python/cpython/issues/129139>
* <https://github.com/python/cpython/issues/126559>
* <https://github.com/python/cpython/issues/130744>
* `python/cpython#129619 <https://github.com/python/cpython/issues/129619>`__
* `python/cpython#129139 <https://github.com/python/cpython/issues/129139>`__
* `python/cpython#126559 <https://github.com/python/cpython/issues/126559>`__
* `python/cpython#130744 <https://github.com/python/cpython/issues/130744>`__

It is the author's opinion that attempting to preserve single-threaded performance
for mutable objects *and* any sort of thread safe parallel execution for the same object is wishful thinking.
Expand All @@ -475,7 +475,7 @@ Object state

Recording object state requires space in the object header, at least 3 bits but no more than a byte.
Each object also needs additional space to refer to its thread, or protecting mutex.
With these fields, the ``PyObject`` header should be the smaller than is currently implemented for PEP 703,
With these fields, the ``PyObject`` header should be the smaller than is currently implemented for :pep:`703`,
although larger than for the default (with GIL) build.

A possible object header:
Expand Down Expand Up @@ -543,11 +543,11 @@ Possible future enhancements
Deep freezing and deep transfers
--------------------------------

Freezing a single object could leave a frozen object with references to mutable objects, and transfering of single objects could leave an object local to one thread, while other objects that it refers to are local to a different thread.
Freezing a single object could leave a frozen object with references to mutable objects, and transferring of single objects could leave an object local to one thread, while other objects that it refers to are local to a different thread.
Either of these scanarios are likely to lead to runtime errors. To avoid that problem we need "deep" freezing.

Deep freezing an object would freeze that object and the transitive closure of other mutable objects referred to by that object.
Deep transfering an object would transfer that object and the transitive closure of other local objects referred to by that object,
Deep transferring an object would transfer that object and the transitive closure of other local objects referred to by that object,
but would raise an exception if one of the those objects belonged to a different thread.

Similar to freezing, a "deep" put mechanism could be added to ``Channel``\ s to move a whole graph of objects from one thread
Expand All @@ -565,17 +565,6 @@ Open Issues
[Any points that are still being decided/discussed.]


Footnotes
=========

.. [1] https://en.wikipedia.org/wiki/Therac-25

.. [2] https://dl.acm.org/doi/10.1145/3704859

.. [3] https://wrigstad.com/pldi2025.pdf



Copyright
=========

Expand Down
Loading
0