-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
PEP 805: Safe Parallel Python #4579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
markshannon
wants to merge
17
commits into
python:main
Choose a base branch
from
markshannon:safe-parallel
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
4d0c768
Initial draft
markshannon 8943f83
Fix format
markshannon 45e783f
Give PEP a number
markshannon 58b5a31
Today's date
markshannon a224703
Fix links
markshannon c5759cc
Attempt to fix code blocks
markshannon 21b8d4f
Now 805
markshannon 1805d9e
Fix header until discuss links is available
markshannon 40cc8f6
Add newlines
markshannon 2f49abf
Fix ref
markshannon 2fa8f80
Apply suggestions from code review
markshannon 1aa961c
Merge branch 'main' into safe-parallel
markshannon 49a2e66
Apply Hugo's format fixes and spelling corrections
markshannon 3fdd987
Fix links
markshannon 6ef78e1
Add back creation date
markshannon 7d99716
Add some spaces. Sphinx likes spaces
markshannon 78261bd
Update peps/pep-0805.rst
pablogsal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Apply Hugo's format fixes and spelling corrections
Co-authored-by: Hugo van Kemenade <[email protected]>
- Loading branch information
commit 49a2e664e2bc87844ecf6d442fb016d0ff8c8245
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ Author: Mark Shannon <[email protected]> | |
| Discussions-To: Pending | ||
| Status: Draft | ||
| Type: Standards Track | ||
| Created: 08-Sep-2025 | ||
| Python-Version: 3.15 | ||
|
|
||
| Abstract | ||
| ======== | ||
|
|
@@ -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: | ||
|
|
@@ -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 | ||
| ============= | ||
|
|
@@ -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. | ||
|
|
@@ -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 | ||
| ----------------------- | ||
|
|
@@ -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. | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
| ------------------ | ||
|
|
@@ -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 | ||
| -------------------- | ||
|
|
@@ -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. | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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, | ||
|
|
@@ -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. | ||
|
|
@@ -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: | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
| ========= | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.