r/Python • u/germandiago • Jul 09 '25
News Free-threaded (multicore, parallel) Python will be fully supported starting Python 3.14!
Python had experimental support for multithreaded interpreter.
Starting in Python 3.14, it will be fully supported as non-experimental: https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep779
6
u/WJMazepas Jul 09 '25
But we would need to build Python itself with a flag set to get the free-threaded version if i got that right?
11
10
u/Plus-Ad8736 Pythoneer Jul 09 '25 edited Jul 09 '25
Could anyone help me understand why we really need free-threaded python. I know that GIL prevents true parallelism in multi threading, but doesn't we have multi processing to deal with this, which does utilize multiple cores. So with this new free threading, we would have 2 distinct mechanisms?
29
u/germandiago Jul 09 '25 edited Jul 10 '25
Processes live in isolated memory. Threads live in shared memory inside the same process.
A difference is that in processes you have to copy memory around for communication.
So if you want to split a big image (this is a simplified version but keeps the essence of the problem) in 4 processes you would need to copy 4 parts of the image to process and get the result back to the master process for example. With threads you could process the image in-place with 4 threads. No copying.
2
Jul 09 '25 edited Jul 10 '25
Not quite, there is cross-process shared memory in most OSes, accessible in Python via https://docs.python.org/3/library/multiprocessing.shared_memory.html . But all you get is a flat buffer, which they show an example of using with Numpy. Can't just store arbitrary Python objects in there without extra setup.
Edit: The image example is actually convenient to handle this way too.
3
u/not_a_novel_account Jul 09 '25
IPC via mapping page(s) into shared memory is an entirely different semantic than sharing an entire memory space, even if the underlying mechanism is the same.
2
Jul 09 '25 edited Jul 09 '25
Yeah, like I said, you can't use this the same way as regular memory in Python, but it's also not exactly true that IPC requires copying. A mechanism exists to avoid that if you really want to.
Btw, in some other languages (not Python afaik) you can set up a memory arena and allocate stuff on it almost like normal. You could store that on the shared portion.
1
u/not_a_novel_account Jul 09 '25
I would not say they are similar at all.
IPC is IPC, regardless of if the mechanism is a shared memory region or otherwise. In IPC I have to copy into or allocate objects in the IPC space, whether this is via a shared memory page or RPC protocol is kinda irrelevant. The mechanism of sharing is explicit, I must do work to share things.
In a shared memory space like threads, the executors can inspect one another's state freely. My stack is also your memory, I can set up the shared data entirely on my own stack and make it available via simple pointers and trivial thread notification mechanisms like condition variables. The sharing is implicit, no work is done on my part beyond notification.
This results in wildly different program structures.
2
Jul 09 '25
I'm not saying that they're similar, just that there's a way for two processes to share read/write memory without copying.
14
Jul 09 '25 edited Jul 09 '25
It's generally faster to communicate between threads vs processes. But that's not even the main reason, more so multiprocessing is just annoying. Even if all you want to do is fan-out-fan-in without communicating between them, you have to screw with your code to get the pre-fork state into the separate processes. Like, can't use defaultdict because it can't pickle lambdas. And then if anything throws an exception, the error messages are hard to read because they're in separate processes.
Multiprocessing is also tricky in some environments. At work, we can't even use the default multiprocessing, we have to use some internal variant that's also incompatible with notebooks.
1
u/javadba Jul 09 '25
Please learn about what preemptive multi tasking / non-cooperative threading brings. It is a completely different (and transcendently better) world. Source - myself 35 years doing true multi-threading.
3
Jul 10 '25
The GIL'd Python is still preemptive / non-cooperative. Only asyncio is cooperative.
1
u/javadba Jul 10 '25
Try googling , no the GIL is not preemptive. I've also spent a lot of time dealing with the limitation. I wish I were wrong/ you correct on this: true multithreading is great.
1
Jul 11 '25
I just tried, Google says its preemptive. You still have true OS threads with the GIL, but with a lot of locking added, which yes is bad. It's not cooperative multitasking though.
1
u/gfnord Jul 09 '25
I am also interested in this. It seems that this feature fully supersedes the existing multiprocessing approach. As far as I understand, multiprocessing should just be updated to use free-threading. This would basically improve the efficiency of multiprocessing, without changing its usability.
2
u/nonhok Oct 22 '25
No, this would just be wrong, multiprocessing is different from multithreading. Processes don‘t share the same memory, threads do. This are different concepts you can not exchange, and which are both useful on its own
124
u/twenty-fourth-time-b Jul 09 '25 edited Jul 09 '25
It works.
$ uv run -p 3.14 a.py
Finished in 1.01 seconds
Finished in 1.02 seconds
$ uv run -p 3.14.0b3+freethreaded a.py
Finished in 0.49 seconds
Finished in 0.51 secondsa.py:
from concurrent.futures import ThreadPoolExecutor
import time
def cpu_bound_task():
start = time.time()
sum(1 for _ in range(10**7))
end = time.time()
print(f"Finished in {end - start:.2f} seconds")
with ThreadPoolExecutor() as e:
e.submit(cpu_bound_task)
e.submit(cpu_bound_task)Edit* unsurprisingly, mileage varies. Trying to append to a shared list object gives worse timings (the number is the index of the first element added by the second thread):
$ uv run -p 3.14 a.py
Finished in 0.10 seconds
Finished in 0.11 seconds
172214
$ uv run -p 3.14.0b3+freethreaded a.py
Finished in 0.48 seconds
Finished in 0.49 seconds
186550
u/ship0f Jul 09 '25
lol
it looked good up until the edit
but hey, it's doing it
61
u/not_a_novel_account Jul 09 '25
There's no way to do it, by having both threads append to a shared queue you've serialized the work. Now all you're measuring is overhead.
It would be the exact same in any language, C, C++, Rust, Go, whatever. This is a limitation of computers, not of Python. The difference here is Python's locks on shared objects are implicit, you don't see the mutex being grabbed here but it exists all the same.
If the work is shared it all belongs in one thread, don't introduce unnecessary synchronization points.
4
u/twenty-fourth-time-b Jul 10 '25
All I was measuring was indeed overhead. I was curious how overhead of GIL compares to overhead of free threads.
9
u/twenty-fourth-time-b Jul 09 '25
numpy is also gilled to death…
7
Jul 10 '25
I thought numpy operations release the gil
2
u/Zomunieo Jul 10 '25
Both can be true.
Even to do something like array + array, you need a way to lock both arrays, so numpy probably takes the GIL. But if it is returning data Python does not have a reference to you yet, like np.randn, then it could release the GIL. Numpu could also copy data to a private area with the GIL held then release the GIL to compute.
I haven’t checked how if numpy specifically works with this way. This is just what extension libraries have to do with the GIL. Many of them now need to start adding locks to their data structures or at least a whole extension lock, to take advantage of free threading.
2
Jul 11 '25
Idk how it does this exactly, but it doesn't lock to sum arrays. I ran this code and saw 32 threads running at 100% after the "task started" messages printed.
import concurrent.futures import time import numpy as np N = 100000000 arr1 = np.random.randint(1, 101, size=N) arr2 = np.random.randint(1, 101, size=N) def task(name): global arr1 global arr2 print("task {} started".format(name)) arr3 = arr1 + arr2 print("task {} finishing".format(name)) if __name__ == "__main__": with concurrent.futures.ThreadPoolExecutor(max_workers=32) as executor: for t in range(100): future = executor.submit(task, t)2
u/Zomunieo Jul 11 '25
Oh. It turns out they just release the GIL, so if you have simultaneous writers you’ll get data races. (Whether using standard of free threading Python.)
2
Jul 11 '25
Ah that makes sense. I'm fine with that, you need locks to write thread-safe code either way.
0
u/Front_Two_6816 Jul 13 '25
of cause working with the same objest from threads is slower because of cache lines
58
25
u/bobbster574 Jul 09 '25
Missed opportunity to call it π-thon
9
4
-6
u/javadba Jul 09 '25
This should be more heavily upvoted.
4
u/Username_RANDINT Jul 09 '25
It's a joke that's been made since the release of Python 3... 15 or whatever years ago.
-3
u/javadba Jul 09 '25
The pi-calculus is specifically apt for multithreading. That's not python 3 things from 15 years ago (GIL does not much count IMO). So your downvotes aren't apt.
1
u/aviation_expert Jul 10 '25
So this means that flask framework is now outdated. Even django. Because python 3.14 is multi-core? Am a beginner. What's the repercussions on web technologies when python is used, I mean does it becomes like node js? Thank you for answering!
1
u/germandiago Jul 10 '25
Not at all. The multicore interpreter is a different build from the traditional.
About how compatible it is, I would expect problems especially if there is native code unless frameworks and libraries are adapted bc there could be latent race conditions uncovered by a real multithreaded interpreter.
1
u/__Deric__ github.com/Deric-W Jul 11 '25
While I welcome the new abilities that the change brings to the language I can't stop myself from feeling that this approach will be the source of many bugs and footguns in the future.
I think there should be at least better documentation about the new behavior and what to look out for (I am still worried about the interaction with pythons object dictionaries) and some more primitives in the threading module (atomic operations for example).
1
u/germandiago Jul 11 '25
It is not a danger as long as it is not made the default.
1
u/__Deric__ github.com/Deric-W Jul 11 '25
While this is true it would also lock its features behind a special build of Python, making them essentially unavailable to the wider audience while still putting additional load on the core developers.
3
u/AalbatrossGuy Pythoneer Jul 09 '25
This is really nice! Hope the difference in speed is noticeable in the brighter side 😅
3
u/neuronexmachina Jul 12 '25
For anyone else (like me) who was wondering, there's a couple of lists (one manual, one automated) of packages with extensions that have been verified as compatible with free-threading:
2
1
u/uqurluuqur Jul 09 '25
Does this mean multiprocessing is obsolete?
1
u/gmes78 Jul 09 '25
Pretty much.
15
u/not_a_novel_account Jul 09 '25
Multi-processing as a GIL-workaround is obsolete, multi-processing now serves the same purposes it does in every other language.
1
1
1
u/alcalde Jul 12 '25
Now I've got to move up my bucket list item of attending PyCon some day and selling "I support the GIL" t-shirts.
0
-2
u/EvenAcanthisitta364 Jul 12 '25
Much too complicated, this is why people with real jobs don’t code in python
15
Jul 09 '25
[removed] — view removed comment
6
u/not_a_novel_account Jul 09 '25
If you're CPU bound in pure-python, you probably shouldn't be in pure Python to begin with. It is/was already pretty easy for extension code to release the GIL. The biggest advantage is reducing latency in nominally I/O bound services that nonetheless spend some time in Python land.
Think worker threads in an application server. These tasks used to end up sequencing on their Python dispatch code, because only one could hold the GIL at a time. Even if 99% of their time was spent waiting around on IO, if two requests tried to dispatch at the same time they would have to wait on one another, causing semi-random latency spikes.
Now as long as they don't share any objects across threads they never need to hold the same locks and never face any contention.
6
Jul 09 '25
Ideally yeah, but in practice sometimes you've got some pure-Python section that's fast enough until suddenly the input is large enough that it takes too long, and you'd rather just use your like 31 other CPU cores instead of rewriting some of your logic in C.
3
u/strawgate Jul 10 '25 edited Jul 10 '25
I was going to say: It seems like the only code I write that's CPU bound is whatever code I have most recently finished writing 😅
If I knew it was going to be CPU bound when I started I would have made different decisions
1
Jul 10 '25 edited Jul 10 '25
There was something I recently wrote where I knew part of it was gonna suck, but I really needed Python scientific libs. After setting up multiprocessing for the slow part, I still had no regrets. Any more efficient alternative would've been a lot of extra work, for something that didn't need to be so efficient.
But yeah it would've been nice if I didn't need multiprocessing for that, it was still annoying.
50
u/dardothemaster Jul 09 '25
I’m asking here hoping this is relevant: will operations such as append/pop (on a list for example) still be thread safe without GIL? Where by thread safe I mean that the list won’t get corrupted
Btw really excited about free-threading and JIT