Jahrelang hieß parallele Verarbeitung in Python vor allem eins: Kompromisse. Threads helfen bei I/O, aber nicht bei Rechenlast. Prozesse helfen bei Rechenlast, kosten aber Speicher und Startzeit. Mit Python 3.14 verschiebt sich dieses Bild deutlich. Der Free-threaded Build ohne GIL ist jetzt offiziell unterstützt, Subinterpreter sind in der Standardbibliothek angekommen, und auch multiprocessing, concurrent.futures und asyncio haben nützliche Neuerungen bekommen.
In diesem Beitrag bekommst du einen Überblick über alle Wege, mit denen du in Python 3.14 parallel oder nebenläufig arbeiten kannst, samt Codebeispielen und einer Entscheidungshilfe, welcher Ansatz zu deinem Problem passt.
Das Grundproblem: der Global Interpreter Lock (GIL)
Der GIL ist eine Sperre im Standard-Interpreter CPython. Er sorgt dafür, dass immer nur ein Thread gleichzeitig Python-Bytecode ausführt. Das macht die interne Speicherverwaltung einfach und schnell, hat aber eine unangenehme Folge: Startest du vier Threads für eine rechenintensive Aufgabe, laufen sie nicht wirklich parallel, sondern wechseln sich ab. Auf einem Rechner mit acht Kernen nutzt du so praktisch nur einen.
Bei I/O-lastigen Aufgaben (Netzwerkanfragen, Dateizugriffe, Datenbankabfragen) ist das weniger schlimm, weil Threads den GIL freigeben, während sie warten. Bei reiner Rechenarbeit war der GIL aber bisher der Flaschenhals schlechthin.
Die Möglichkeiten in Python 3.14 im Überblick
Python 3.14 bietet dir vier grundlegende Werkzeuge. Sie lösen unterschiedliche Probleme, und oft ist die Wahl zwischen ihnen wichtiger als jede Mikro-Optimierung.
1. Free-threaded Python: Threads ohne GIL
Die größte Neuerung für parallele Verarbeitung in Python 3.14 ist der Status des Free-threaded Builds. In Python 3.13 war er noch experimentell. Mit PEP 779 ist er in 3.14 offiziell unterstützt. Das bedeutet: Er ist kein Spielzeug mehr, sondern eine echte Option für produktiven Code. Der Standard-Build mit GIL bleibt aber weiterhin die Voreinstellung.
So installierst du den Free-threaded Build
Der Free-threaded Build ist eine eigene Variante des Interpreters, erkennbar am t im Namen, also python3.14t. Du bekommst ihn zum Beispiel so:
- Offizielle Installer für Windows und macOS: Dort kannst du die Free-threaded-Variante als optionale Komponente mitinstallieren.
- uv:
uv python install 3.14t - pyenv:
pyenv install 3.14t - Docker oder Linux-Distributionen: viele bieten eigene Pakete wie
python3.14-nogiloder ähnlich an.
Ob der GIL in deiner laufenden Sitzung aktiv ist, prüfst du so:
|
1 2 3 4 |
import sys print(sys.version) print("GIL aktiv:", sys._is_gil_enabled()) |
Ein Beispiel mit echter Parallelität
Der Clou: Du musst deinen Code nicht umschreiben. Derselbe ThreadPoolExecutor, der im normalen Build kaum schneller ist als eine einfache Schleife, nutzt unter python3.14t alle Kerne.
|
1 2 3 4 5 6 7 8 9 10 11 |
import math import time from concurrent.futures import ThreadPoolExecutor def rechne(n): return sum(math.isqrt(i) for i in range(n)) start = time.perf_counter() with ThreadPoolExecutor(max_workers=4) as pool: ergebnisse = list(pool.map(rechne, [5_000_000] * 4)) print(f"Dauer: {time.perf_counter() - start:.2f} s") |
Führe das Skript einmal mit python3.14 und einmal mit python3.14t aus. Auf einem Rechner mit mindestens vier Kernen siehst du im Free-threaded Build eine deutliche Beschleunigung, oft nahe am Faktor vier.
Worauf du achten musst
- Single-Thread-Overhead: Der Free-threaded Build ist bei rein sequenziellem Code etwas langsamer als der Standard-Build. In 3.14 ist dieser Nachteil gegenüber 3.13 deutlich geschrumpft und liegt grob im einstelligen Prozentbereich, je nach Plattform und Workload.
- C-Erweiterungen: Erweiterungsmodule müssen ausdrücklich erklären, dass sie ohne GIL sicher sind. Importierst du ein Modul, das das nicht tut, aktiviert Python den GIL automatisch wieder und gibt eine Warnung aus. Mit
PYTHON_GIL=0oder-X gil=0kannst du das erzwingen, aber nur, wenn du weißt, was du tust. - Race Conditions: Ohne GIL laufen deine Threads wirklich gleichzeitig. Gemeinsam genutzte veränderliche Daten schützt du mit
threading.Lockoder nutzt thread-sichere Strukturen wiequeue.Queue.
2. Subinterpreter: Parallelität im selben Prozess
Die zweite große Neuerung ist PEP 734: Mehrere Interpreter sind jetzt über das neue Modul concurrent.interpreters direkt aus Python nutzbar. Jeder Subinterpreter hat seinen eigenen Zustand und seinen eigenen GIL. Dadurch laufen sie auch im normalen Build mit GIL echt parallel.
Du kannst dir Subinterpreter als Mittelweg zwischen Threads und Prozessen vorstellen: Sie sind voneinander isoliert wie Prozesse, leben aber im selben Betriebssystemprozess und starten schneller.
Der einfache Weg: InterpreterPoolExecutor
Am bequemsten nutzt du Subinterpreter über den neuen InterpreterPoolExecutor. Er hat dieselbe Schnittstelle wie ThreadPoolExecutor und ProcessPoolExecutor:
|
1 2 3 4 5 |
# rechenkern.py import math def rechne(n): return sum(math.isqrt(i) for i in range(n)) |
|
1 2 3 4 5 6 7 8 |
# main.py from concurrent.futures import InterpreterPoolExecutor from rechenkern import rechne if __name__ == "__main__": with InterpreterPoolExecutor(max_workers=4) as pool: ergebnisse = list(pool.map(rechne, [5_000_000] * 4)) print(ergebnisse) |
Die Funktion liegt hier bewusst in einem eigenen Modul. Funktionen und Argumente werden an die Subinterpreter übergeben und müssen dort importierbar beziehungsweise serialisierbar sein. Funktionen aus dem Hauptskript machen dabei schnell Probleme.
Mehr Kontrolle mit concurrent.interpreters
Wenn du direkt mit Interpretern arbeiten willst, bietet dir das Low-Level-Modul mehr Kontrolle:
|
1 2 3 4 5 6 7 8 9 10 |
from concurrent import interpreters interp = interpreters.create() interp.exec("print('Hallo aus dem Subinterpreter')") queue = interpreters.create_queue() # Die Queue kann an andere Interpreter übergeben werden, # um Daten zwischen ihnen auszutauschen. interp.close() |
Einschränkung: Nicht jedes C-Erweiterungsmodul unterstützt bereits mehrere Interpreter. Bei reinem Python-Code und der Standardbibliothek bist du auf der sicheren Seite, bei großen Bibliotheken mit C-Code solltest du vorher testen.
3. Multiprocessing: der bewährte Klassiker
Das Modul multiprocessing und der ProcessPoolExecutor starten separate Prozesse mit jeweils eigenem Interpreter. Das umgeht den GIL vollständig und funktioniert mit praktisch jeder Bibliothek. Der Preis: Jeder Prozess braucht eigenen Speicher, und Daten zwischen Prozessen müssen serialisiert werden.
|
1 2 3 4 5 6 7 |
from concurrent.futures import ProcessPoolExecutor from rechenkern import rechne if __name__ == "__main__": with ProcessPoolExecutor() as pool: ergebnisse = list(pool.map(rechne, [5_000_000] * 8)) print(ergebnisse) |
Was sich in Python 3.14 ändert
- Neue Standard-Startmethode: Unter Linux und anderen POSIX-Systemen (außer macOS) ist die Voreinstellung jetzt
forkserverstattfork. Das ist sicherer, vor allem in Programmen, die zusätzlich Threads nutzen. macOS und Windows verwenden weiterhinspawn. Wenn dein Code bisher stillschweigend auf geerbten globalen Zustand vertraut hat, kann er dadurch brechen. Brauchst du das alte Verhalten, setzt du es explizit mitmultiprocessing.set_start_method("fork")oder übergibst einen passendenmp_context. - Worker gezielt beenden:
ProcessPoolExecutorhat die neuen Methodenterminate_workers()undkill_workers(). Damit beendest du hängende Worker, ohne auf ihr Ende warten zu müssen. - Speicherschonendes map():
Executor.map()kennt den neuen Parameterbuffersize. Er begrenzt, wie viele Aufgaben gleichzeitig vorgemerkt werden. Das ist praktisch, wenn du sehr große oder unendliche Eingaben verarbeitest.
|
1 2 3 |
with ProcessPoolExecutor() as pool: for ergebnis in pool.map(rechne, riesige_eingabe(), buffersize=100): verarbeite(ergebnis) |
4. asyncio: Nebenläufigkeit für I/O
asyncio ist streng genommen keine parallele Verarbeitung, sondern Nebenläufigkeit: Ein einzelner Thread wechselt zwischen vielen Aufgaben, immer dann, wenn eine davon wartet. Für Webserver, Crawler oder Anwendungen mit tausenden gleichzeitigen Verbindungen ist das oft die effizienteste Lösung.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import asyncio async def lade(nr): await asyncio.sleep(1) # Stellvertretend für eine Netzwerkanfrage return f"Ergebnis {nr}" async def main(): async with asyncio.TaskGroup() as tg: tasks = [tg.create_task(lade(i)) for i in range(100)] print([t.result() for t in tasks][:3]) asyncio.run(main()) |
Hundert Anfragen à eine Sekunde dauern hier insgesamt rund eine Sekunde, nicht hundert.
Neu in 3.14: laufende Tasks untersuchen
Python 3.14 bringt eine praktische Debugging-Hilfe mit. Du kannst von außen in einen laufenden Prozess schauen und siehst, welche Tasks gerade existieren und worauf sie warten:
|
1 2 |
python -m asyncio ps 12345 python -m asyncio pstree 12345 |
Die Zahl ist die Prozess-ID deines Programms. Gerade bei hängenden Anwendungen sparst du dir damit viel Rätselraten. Innerhalb des Programms liefert asyncio.capture_call_graph() ähnliche Informationen.
Welcher Ansatz passt zu dir?
Die wichtigste Frage lautet: Wartet dein Programm vor allem auf etwas (I/O), oder rechnet es (CPU)? Danach entscheidet die Kompatibilität deiner Bibliotheken.
Praxistipps für den Umstieg
- Erst messen, dann parallelisieren. Mit
cProfileodertime.perf_counter()findest du heraus, wo dein Programm wirklich Zeit verliert. - Setze auf concurrent.futures. Da
ThreadPoolExecutor,InterpreterPoolExecutorundProcessPoolExecutordieselbe Schnittstelle haben, kannst du die Varianten mit einer einzigen geänderten Zeile vergleichen. - Teste den Free-threaded Build parallel. Lass deine Testsuite in CI zusätzlich unter
python3.14tlaufen. So siehst du früh, ob deine Abhängigkeiten mitspielen. - Prüfe deine Startmethode. Nach dem Upgrade auf 3.14 unter Linux lohnt sich ein Blick auf Code, der mit
multiprocessingarbeitet und sich aufforkverlassen hat. - Kombiniere Ansätze. Ein asyncio-Webserver kann rechenintensive Teile mit
loop.run_in_executor()an einen Prozess- oder Interpreter-Pool auslagern.
Fazit
Python 3.14 ist ein Wendepunkt für parallele Verarbeitung. Mit dem offiziell unterstützten Free-threaded Build kannst du Threads endlich auch für Rechenlast nutzen. Subinterpreter bieten dir echte Parallelität ohne den Overhead separater Prozesse. Und die bewährten Werkzeuge multiprocessing und asyncio sind durch sinnvolle Detailverbesserungen robuster und besser zu debuggen geworden. Noch ist der GIL-freie Betrieb nicht die Voreinstellung und das Ökosystem zieht schrittweise nach. Jetzt ist aber ein guter Zeitpunkt, deine Projekte darauf vorzubereiten.
Alle Details zu den Neuerungen findest du in der offiziellen Übersicht What’s New in Python 3.14 und in der Anleitung Python support for free threading.
Häufige Fragen zu Python 3.14 und paralleler Verarbeitung
Nein. Der Standard-Build von Python 3.14 hat weiterhin einen GIL. Es gibt aber zusätzlich den offiziell unterstützten Free-threaded Build ( Er ist offiziell unterstützt und damit für den produktiven Einsatz gedacht. Ob er für dein Projekt passt, hängt vor allem davon ab, ob alle genutzten C-Erweiterungen free-threading-fähig sind. Teste deine Anwendung gründlich, bevor du umstellst. Threads teilen sich den gesamten Speicher und alle Objekte. Subinterpreter sind voneinander isoliert und tauschen Daten nur über definierte Kanäle wie Queues aus. Dadurch gibt es weniger Race Conditions, aber auch mehr Aufwand beim Datenaustausch. Ja. Multiprocessing ist die robusteste Lösung, wenn du Bibliotheken nutzt, die weder Free-threading noch Subinterpreter unterstützen, oder wenn du maximale Isolation willst. Stürzt ein Prozess ab, bleiben die anderen davon unberührt. Nicht automatisch. Mit dem Standard-Build ändert sich an der Parallelität deiner Threads nichts. Nutzt du bereits Nein, asyncio ist nebenläufig. Alle Tasks laufen in einem Thread und wechseln sich an
Ist der GIL in Python 3.14 abgeschafft?
python3.14t), der ohne GIL läuft. Du entscheidest selbst, welchen Interpreter du verwendest.Ist der Free-threaded Build schon produktionsreif?
Was ist der Unterschied zwischen Subinterpretern und Threads?
Brauche ich multiprocessing überhaupt noch?
Wird mein bestehender Code mit Python 3.14 schneller?
ThreadPoolExecutor für Rechenlast, kann der Wechsel auf python3.14t ohne Codeänderung einen deutlichen Geschwindigkeitsgewinn bringen.Ist asyncio parallel?
await-Stellen ab. Für I/O-lastige Aufgaben ist das sehr effizient, für Rechenlast brauchst du Threads, Subinterpreter oder Prozesse.
