Unterstützung von freiem Threading in Python
********************************************

Seit der Veröffentlichung von 3.13 unterstützt CPython einen Python-
Build namens *freies Threading*, bei dem der *Global Interpreter Lock*
(GIL) abgeschaltet ist. Die freie Ausführung von Threads erlaubt es,
die verfügbare Rechenleistung voll auszuschöpfen, indem Threads
parallel auf den vorhandenen CPU-Kernen laufen. Nicht jede Software
profitiert davon automatisch, aber Programme, die von Anfang an auf
Threads ausgelegt sind, laufen auf Mehrkern-Hardware schneller.

Manche Pakete von Dritten, insbesondere solche mit einem
*Erweiterungsmodul*, sind für den Einsatz in einem Free-Threaded-Build
möglicherweise noch nicht bereit und schalten den *GIL* wieder ein.

Dieses Dokument beschreibt, was freies Threading für Python-Code
bedeutet. Wie du C-Erweiterungen schreibst, die den Free-Threaded-
Build unterstützen, steht unter Unterstützung von freiem Threading in
C-API-Erweiterungen.

Siehe auch:

  **PEP 703** – Making the Global Interpreter Lock Optional in
  CPython, für eine Gesamtbeschreibung von Python mit freiem
  Threading.


Installation
============

Seit Python 3.13 können die offiziellen Installationsprogramme für
macOS und Windows auf Wunsch auch die Free-Threaded-Programmdateien
von Python installieren. Die Installationsprogramme findest du unter
https://www.python.org/downloads/.

Informationen zu anderen Plattformen findest du unter Installing a
Free-Threaded Python, einer von der Gemeinschaft gepflegten Anleitung
zur Installation von Python mit freiem Threading.

Wenn du CPython aus den Quellen baust, solltest du die configure-
Option "--disable-gil" verwenden, um einen Python-Interpreter mit
freiem Threading zu bauen.


Python mit freiem Threading erkennen
====================================

Um zu prüfen, ob der aktuelle Interpreter freies Threading
unterstützt: "python -VV" und "sys.version" enthalten „free-threading
build“. Mit der neuen Funktion "sys._is_gil_enabled()" lässt sich
prüfen, ob der GIL im laufenden Prozess tatsächlich abgeschaltet ist.

Über die Konfigurationsvariable
"sysconfig.get_config_var("Py_GIL_DISABLED")" lässt sich feststellen,
ob der Build freies Threading unterstützt. Ist die Variable auf "1"
gesetzt, unterstützt der Build freies Threading. Das ist der
empfohlene Weg für Entscheidungen, die von der Build-Konfiguration
abhängen.


Der Global Interpreter Lock in Python mit freiem Threading
==========================================================

Free-Threaded-Builds von CPython können auf Wunsch zur Laufzeit auch
mit eingeschaltetem GIL laufen – über die Umgebungsvariable
"PYTHON_GIL" oder die Befehlszeilenoption "-X gil".

Der GIL kann außerdem automatisch eingeschaltet werden, wenn ein C
-API-Erweiterungsmodul importiert wird, das nicht ausdrücklich als
„unterstützt freies Threading“ gekennzeichnet ist. In diesem Fall wird
eine Warnung ausgegeben.

Neben der Dokumentation der einzelnen Pakete verfolgen die folgenden
Webseiten, wie weit beliebte Pakete freies Threading unterstützen:

* https://py-free-threading.github.io/tracking/

* https://hugovk.github.io/free-threaded-wheels/


Threadsicherheit
================

The free-threaded build of CPython aims to provide similar thread-
safety behavior at the Python level to the default GIL-enabled build.
Built-in types like "dict", "list", and "set" use internal locks to
protect against concurrent modifications in ways that behave similarly
to the GIL.  See Garantien zur Threadsicherheit for the guarantees
provided by built-in types.

Bemerkung:

  Es ist empfehlenswert, wo möglich "threading.Lock" oder andere
  Synchronisationsprimitive zu verwenden, anstatt sich auf die
  internen Sperren der integrierten Typen zu verlassen.


Bekannte Einschränkungen
========================

Dieser Abschnitt beschreibt bekannte Einschränkungen des Free-
Threaded-Builds von CPython.


Unsterblichkeit
---------------

Im Free-Threaded-Build sind manche Objekte *unsterblich*. Unsterbliche
Objekte werden nicht freigegeben, und ihre Referenzzähler werden nie
verändert. Damit wird ein Gedränge um die Referenzzähler vermieden,
das eine effiziente Skalierung über mehrere Threads verhindern würde.

Mit der Veröffentlichung von 3.14 ist die Unsterblichkeit beschränkt
auf:

* Codekonstanten: Zahlenliterale, Zeichenkettenliterale und
  Tupelliterale, die aus anderen Konstanten bestehen.

* Zeichenketten, die mit "sys.intern()" interniert wurden.


Frame-Objekte
-------------

Es ist nicht sicher, von einem Frame-Objekt aus auf "frame.f_locals"
zuzugreifen, wenn dieser Frame gerade in einem anderen Thread
ausgeführt wird. Der Interpreter kann dabei abstürzen.


Iteratoren
----------

Es ist im Allgemeinen nicht threadsicher, aus mehreren Threads
gleichzeitig auf dasselbe Iteratorobjekt zuzugreifen. Die Threads
können doppelte oder fehlende Elemente sehen.


Leistung bei einem einzelnen Thread
-----------------------------------

Der Free-Threaded-Build hat beim Ausführen von Python-Code einen
zusätzlichen Aufwand gegenüber dem Standard-Build mit eingeschaltetem
GIL. Wie groß dieser Aufwand ist, hängt von der Arbeitslast und der
Hardware ab. In der Benchmark-Sammlung pyperformance reicht der
durchschnittliche Aufwand von etwa 1 % auf macOS aarch64 bis zu 8 %
auf x86-64-Linux-Systemen.


Verhaltensänderungen
====================

Dieser Abschnitt beschreibt Verhaltensänderungen von CPython im Free-
Threaded-Build.


Kontextvariablen
----------------

Im Free-Threaded-Build ist das Flag "thread_inherit_context"
standardmäßig auf wahr gesetzt. Dadurch starten Threads, die mit
"threading.Thread" erzeugt werden, mit einer Kopie des "Context()" des
Aufrufers von "start()". Im Standard-Build mit eingeschaltetem GIL ist
das Flag standardmäßig falsch, sodass Threads mit einem leeren
"Context()" starten.


Warnungsfilter
--------------

Im Free-Threaded-Build ist das Flag "context_aware_warnings"
standardmäßig auf wahr gesetzt. Im Standard-Build mit eingeschaltetem
GIL ist das Flag standardmäßig falsch. Ist das Flag wahr, verwendet
der Kontextmanager "warnings.catch_warnings" eine Kontextvariable für
die Warnungsfilter. Ist das Flag falsch, verändert "catch_warnings"
die globale Filterliste, was nicht threadsicher ist. Näheres steht
beim Modul "warnings".


Höherer Speicherverbrauch
-------------------------

Der Free-Threaded-Build verbraucht gegenüber dem Standard-Build
üblicherweise mehr Speicher. Dafür gibt es mehrere Gründe, die
größtenteils auf Entwurfsentscheidungen zurückgehen.


Alle internierten Zeichenketten sind unsterblich
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In modernen Python-Versionen (seit Version 2.3) macht das Internieren
einer Zeichenkette (etwa mit "sys.intern()") diese nicht unsterblich.
Verschwindet stattdessen die letzte Referenz auf diese Zeichenkette,
wird sie aus der Tabelle der internierten Zeichenketten entfernt. Im
Free-Threaded-Build ist das nicht so: Jede internierte Zeichenkette
wird unsterblich und überlebt bis zum Herunterfahren des Interpreters.


Objekte außerhalb der Speicherbereinigung haben einen größeren Objektkopf
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Der Free-Threaded-Build verwendet eine andere "PyObject"-Struktur.
Anstatt die Informationen zur Speicherbereinigung vor der
"PyObject"-Struktur anzulegen, wie im Standard-Build, sind diese
Informationen Teil des normalen Objektkopfs. Auf der AMD64-Plattform
belegt "None" im Free-Threaded-Build zum Beispiel 32 Bytes gegenüber
16 Bytes im Standard-Build. Objekte, die der Speicherbereinigung
unterliegen (etwa Dicts und Listen), sind in beiden Builds gleich
groß, weil der Free-Threaded-Build für die Informationen zur
Speicherbereinigung keinen zusätzlichen Platz braucht.


QSBR kann die Freigabe von Speicher verzögern
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Um sperrfreie Datenstrukturen sicher umzusetzen, wird ein Verfahren
zur sicheren Speicherrückgewinnung (safe memory reclamation, SMR)
eingesetzt, bekannt als quiescent state-based reclamation (QSBR). Das
bedeutet, dass der Speicher hinter Datenstrukturen mit sperrfreiem
Zugriff QSBR verwendet, was die Freigabe aufschiebt, anstatt den
Speicher sofort freizugeben. Zwei Beispiele für solche Datenstrukturen
sind das Listenobjekt und das Objekt mit den Schlüsseln eines
Wörterbuchs. Näheres dazu, wie QSBR umgesetzt ist, steht in
"InternalDocs/qsbr.md" im Quellbaum von CPython. Ein Aufruf von
"gc.collect()" sollte dazu führen, dass der ganze von QSBR gehaltene
Speicher tatsächlich freigegeben wird. Beachte, dass die
darunterliegende Speicherverwaltung diesen Speicher selbst dann, wenn
QSBR ihn freigibt, möglicherweise nicht sofort an das Betriebssystem
zurückgibt. Die Resident Set Size (RSS) des Prozesses sinkt dann also
nicht.


Die Speicherverwaltung mimalloc gegenüber pymalloc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Der Standard-Build verwendet für kleine Speicheranforderungen (512
Bytes oder kleiner) normalerweise die Speicherverwaltung „pymalloc“.
Der Free-Threaded-Build verwendet pymalloc nicht und legt alle Python-
Objekte mit der Speicherverwaltung „mimalloc“ an. Die
Speicherverwaltung pymalloc hat die folgenden Eigenschaften, die den
Speicherverbrauch niedrig halten: geringer Aufwand je angelegtem
Block, wirksame Vermeidung von Speicherfragmentierung und schnelle
Rückgabe freien Speichers an das Betriebssystem. Die
Speicherverwaltung mimalloc schlägt sich in diesen Punkten ebenfalls
recht gut, kann aber etwas mehr Aufwand mitbringen.

Im Free-Threaded-Build verwaltet mimalloc den Speicher in mehreren
getrennten Halden (derzeit vier). So werden zum Beispiel alle Objekte,
die die Speicherbereinigung unterstützen, aus ihrer eigenen Halde
angelegt. Getrennte Halden bedeuten, dass freier Speicher in einer
Halde nicht für eine Anforderung aus einer anderen Halde genutzt
werden kann. Außerdem sind manche Halden so eingerichtet, dass sie
QSBR (quiescent-state based reclamation) verwenden, wenn der Speicher
freigegeben wird, der die Halde unterlegt (in der Begriffswelt von
mimalloc „Pages“ genannt). Durch den Einsatz von QSBR entsteht eine
Verzögerung zwischen der Freigabe aller Speicherblöcke einer Page und
der Freigabe der Speicherpage selbst – entweder für neue Anforderungen
oder zurück an das Betriebssystem.

Auch die Speicherverwaltung mimalloc schiebt die Rückgabe
freigegebenen Speichers an das Betriebssystem auf. Du kannst diese
Verzögerung verkürzen, indem du die Umgebungsvariable
"MIMALLOC_PURGE_DELAY" auf "0" setzt. Beachte, dass das die Leistung
der Speicherverwaltung wahrscheinlich verschlechtert.


Referenzzählung mit freiem Threading kann Objekte länger leben lassen
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Im Standard-Build wird ein Objekt normalerweise freigegeben, sobald
sein Referenzzähler null erreicht. Der Free-Threaded-Build verwendet
„biased reference counting“ mit einem schnellen Weg für Objekte, die
dem aktuellen Thread „gehören“, und einem langsamen Weg für andere
Objekte. Weitere Details stehen in **PEP 703**. Immer wenn der
Referenzzähler eines Objekts in einem „eingereihten“ Zustand landet,
kann die Freigabe aufgeschoben werden. Der eingereihte Zustand wird im
Abschnitt „eval breaker“ des Bytecode-Auswerters aufgelöst.

Der Free-Threaded-Build erlaubt außerdem eine andere Art der
Referenzzählung, bekannt als „deferred reference counting“
(aufgeschobene Referenzzählung). Diese Betriebsart wird pro Objekt
über ein Flag eingeschaltet. Für die folgenden Typen ist die
aufgeschobene Referenzzählung eingeschaltet:

* Modulobjekte

* Funktionen auf der obersten Ebene eines Moduls

* Klassenmethoden, die im Klassenbereich definiert sind

* Deskriptor-Objekte

* thread-lokale Objekte, erzeugt mit "threading.local"

Ist die aufgeschobene Referenzzählung eingeschaltet, werden Referenzen
von den Stapeln von Python-Funktionen nicht in den Referenzzähler
aufgenommen. Dieses Verfahren verringert den Aufwand der
Referenzzählung, besonders für Objekte, die aus mehreren Threads
verwendet werden. Weil die Referenzen auf dem Stapel nicht gezählt
werden, werden Objekte mit aufgeschobener Referenzzählung nicht sofort
freigegeben, wenn ihr interner Referenzzähler null erreicht. Sie
werden stattdessen beim nächsten Lauf der Speicherbereinigung
untersucht und freigegeben, wenn sich keine Referenzen auf dem Stapel
mehr auf sie finden. Das heißt, dass diese Objekte von der
Speicherbereinigung freigegeben werden und nicht, wie üblich, sobald
ihr Referenzzähler null erreicht.


Referenzzählung pro Thread kann die Freigabe von Objekten verzögern
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Um ein Gedränge um die Referenzzählerfelder häufig gemeinsam genutzter
Objekte zu vermeiden, verwendet der Free-Threaded-Build für einige
ausgewählte Objekttypen außerdem eine „Referenzzählung pro Thread“.
Anstatt einen einzigen gemeinsamen Referenzzähler zu aktualisieren,
führt jeder Thread sein eigenes lokales Feld von Referenzzählern,
indiziert über eine dem Objekt zugewiesene eindeutige Kennung. Der
tatsächliche Referenzzähler wird erst dann berechnet, wenn der lokale
Zähler des Objekts null erreicht – durch Summieren der Zähler aller
Threads. Die Referenzzählung pro Thread wird derzeit verwendet für:

* Haldentypobjekte (in Python erzeugte Klassen)

* Codeobjekte

* das "__dict__" von Modulobjekten

Weil die Zähler aller Threads vor der Freigabe des Objekts wieder
zusammengeführt werden müssen, werden Objekte mit Referenzzählung pro
Thread üblicherweise später freigegeben als im Standard-Build.
Insbesondere wird ein solches Objekt meist erst dann freigegeben, wenn
der Thread, der es referenziert hat, einen sicheren Punkt erreicht
(zum Beispiel im Abschnitt „eval breaker“ des Bytecode-Auswerters)
oder sich beendet. Ein Aufruf von "gc.collect()" führt die Zähler
aller Threads zusammen und erlaubt die Freigabe dieser Objekte.
