Repository navigation
Free-threading CPython support (tracker issue) #16916
Description
This issue is to discuss and keep track of progress on support for the experimental free-threading build of CPython at a high level.
(#16595 is a preliminary requirement, but a separate problem)
Disclaimer
This is a complex problem that may require a significant number of man-hours, and it doesn't necessarily fit my current role's prerogatives. The reason I'm opening this issue is that I recently spent some of my free time learning about, and working on this topic in other packages I maintain, so astropy gets the leg work done mostly1 for free !
Context
What's the GIL ?
CPython has historically been using a Global Interpreter Lock (GIL) to guarantee thread-safety of multi-threaded applications, which effectively prevents true thread concurrency as only one thread is allowed to execute Python byte code at any given point.
What even is free-threading ?
Earlier this year, PEP 703 was (provisionally) adopted, which defines a new approach to make optional at build time.
This is a huge change for CPython, and one with backward compatibility implications: existing applications may be relying on the existence of the GIL for thread safety and may break under the new build.
This is the main reason why this change is being introduced as an experimental opt-in first. Python 3.13 is the first version that supports this option. To be clear:
- in no situation should anyone end up running this experimental build unknowingly, the GIL-flavored build is still the one you get if you don't know anything about this topic.
- pure Python code remains mostly2 thread-safe under free-threading as object-level locks replace the GIL on fundamental data structures. For instance,
list.appendcan still be run on a sharedlistwithout running into races. - the main problem is with low-level extensions (C, C++ and Cython in astropy's case)
Why should we care ?
The plan is that down the road (about 4 years from now), this becomes the only build for CPython, so it cannot be ignored.
While there might not be known multi-threaded astropy applications at the moment, it is more likely that end-users and downstream developers might want to give it a try in the near future now that we know the limitations from the GIL are set to be overcome.
NumPy is moving rapidly towards compatibility, see NumPy 2.1's release notes
Taking things slow and starting early increases the chance of success both for PEP 703 and the future of astropy.
Levels of support
level 0: build-time compatibility on ubuntu
This is the easiest goal to achieve, as Ubuntu is currently the only platform for which free-threaded builds of Python 3.13 are easily available on GitHub Actions via deadsnakes.
At the time of writing, the latest stable version of Cython doesn't support free-threading, but the dev branch does. Cython 3.1
is set to be the first stable version to support this.
level 1: running tests on a single thread
I do not expect this step to reveal (m)any problem(s), though it is surely best done before going to the next.
level 2: running the test suite concurrently
A cheap strategy to discover thread safety issues is to use https://github.com/Quansight-Labs/pytest-run-parallel
level 3: add dedicated tests to exercise potential races
This step is much more involved, but also much more likely to discover crucial pain points.
While it is virtually impossible to systematically test every possible multi-threaded scenario, we can focus on:
- situations we know (from expertise) that thread-safety is achievable but not guaranteed at the moment (say, adding columns to a
Table, registering units to namespaces, mutating astropy-defined class objects ...) - real-life applications brought up by downstream developers and end-users
As noted in Quansight's documentation, tests designed to discover thread safety issues are expensive because they must be run ~ hundreds of times before one can be confident in their success.
While it seems feasible to go to level 2 at the package level with relative ease, I anticipate that level 3 will require much more work and should probably be conducted at the sub-package level (which might be best discussed as separate issues).
I'm planning to write a call-for-action email to get a feeling of which sub-package maintainers are interested in discussing this at an early stage.
Further resources
Foundational document PEP 703
I also highly recommend anyone interested in this topic to read through the collaborative documentation initiated by the Quansight team.
Linked issues and PRs
- PEP 703: actions/setup-python python3.13t builds with
--disable-gilactions/setup-python#771 - DEP: should we vendorize
pythoncapi-compat? #16839 - ENH: stats: enable free-threading support for Cython extensions #20267
Activity
Cadair commentedon Oct 3, 2024
I spotted this on my travels around the internet today: https://pypi.org/project/pytest-freethreaded/
neutrinoceros commentedon Nov 1, 2024
Matplotlib 3.10's rc release notes also give pointers on how to use it within a free-threaded interpreter; in particular
So I just checked: the only times we do use
pyplotwithin astropy are to fallback toplt.gca()in functions that expect a user-provided axis. I think it's probably fine to keep those (and I don't see a good alternative for it).On the other hand, we do use the
pyplotinterface a lot in tests, which I expect might become an obstacle at some point, but it should be straightforward (if slightly time-consuming) to migrate.(temporary link) ref https://matplotlib.org/devdocs/users/next_whats_new/freethreading.html
pllim commentedon Nov 1, 2024
Also don't forget https://github.com/matplotlib/pytest-mpl
neutrinoceros commentedon Nov 8, 2024
FWIW I did check pytest-mpl and I think it should be safe.
neutrinoceros commentedon Mar 6, 2025
Good news for anyone interested in this: actions/setup-python#771 (comment) was just closed. Next releases of
actions/setup-pythonwill have support for free-threaded builds.Update: this is https://github.com/actions/setup-python/releases/tag/v5.5.0
neutrinoceros commentedon Mar 18, 2025
As an important first step, I'm starting to study py-erfa's needs regarding support free-threading.
eteq commentedon Jun 26, 2025
There was discussion about this at the 2025 coordination meeting - running notes here: https://docs.google.com/document/d/17zwz_7g4XdUOGR3oEtUQAqNbdYTDnxEhrxfDKM0mt7Q/edit?tab=t.0#heading=h.qmm96ghr2wrc
rgommers commentedon Jun 27, 2025
A comment on this part of the discussion notes:
NumPy will not change how it's handling multi-threading, which is: everything is single-threaded in NumPy itself, only the underlying BLAS library for linear algebra functionality will run operations on all available thread (in default BLAS library build configs, it's configurable with
threadpoolctlor environment variables).Same for SciPy - there are keywords (
workers=) in some SciPy APIs likefftandoptimizefor the user to request multiple threads or processes, but the default will be single-threaded. scikit-learn again does the same, except the keyword is calledn_jobs.This is typically the best strategy for libraries (at least in the absence of some cross-library coordination mechanism, which doesn't exist for our stack), since it doesn't give performance pitfalls for users when they combine different libraries and allows the end user to decide if they want to use multiple threads or processes.
neutrinoceros commentedon Sep 8, 2025
Important news for anyone interested: here's the PEP about stabilizing the limited API for free-threaded builds
https://peps.python.org/pep-0803/
Cadair commentedon Sep 8, 2025
Oh that's going to be a royal pain.
neutrinoceros commentedon Sep 9, 2025
Anything specific you're worried about ?
Cadair commentedon Sep 9, 2025
Mainly that it sounds like it's going to be hard to support both versions of the limited API. We might end up having to build "full fat" versions of 3.15+ wheels for free threaded support until we have a min 3.15?
neutrinoceros commentedon Sep 9, 2025
Maybe I missed something along the way but I don't think that's necessarily the case.
rgommers commentedon Sep 9, 2025
I don't think that's the case indeed, it should be straightforward to support building old-style
abi3wheels plus the new 3.15+ wheels at source level.Cadair commentedon Sep 9, 2025
But not supporting the new limited API and the old one at once right? So we'd re-write to the new limited API after we are 3.14< or am I misreading this completely lol
rgommers commentedon Sep 9, 2025
Why not? I was saying that exactly that should be straightforward. Just like it's now not hard to build both
abi3andcp314twheels. You can support both at the same time at source level, and let the build backend pick the right default based on the Python version it's building for.Cadair commentedon Sep 9, 2025
It was this section I think that jumped out at me when I was reading the PEP:
The PEP 793 is C API which will only be added in 3.15 so I assume best case scenario is that we need a block for old API and block for new API?
I know that building isn't going to be an issue, it was the C API changes I thought would be awkward?
rgommers commentedon Sep 9, 2025
You already need several
#if PY_VERSION_HEX >=today, e.g.:https://github.com/numpy/numpy/blob/9b9fe6fc775541135ac287fbba6f43076e4b84b7/numpy/linalg/umath_linalg.cpp#L4751-L4761
I suspect that adding support for PEP 793 is going to end up extending that pattern a bit, and you'll end up copying the pattern from some other project that figured out a clean way of writing the
#if's.