Garantien zur Threadsicherheit
******************************

Diese Seite dokumentiert die Garantien zur Threadsicherheit für
eingebaute Typen im Build mit freien Threads von Python. Die hier
beschriebenen Garantien gelten, wenn Python mit abgeschaltetem *GIL*
läuft (Modus mit freien Threads). Ist der GIL eingeschaltet, werden
die meisten Operationen implizit serialisiert.

Allgemeine Hinweise zum Schreiben threadsicheren Codes in Python mit
freien Threads findest du unter Unterstützung von freiem Threading in
Python.


Stufen der Threadsicherheit
===========================

Die Dokumentation des C-API verwendet die folgenden Stufen, um die
Garantien zur Threadsicherheit jeder Funktion zu beschreiben. Die
Stufen sind von der unsichersten zur sichersten aufgeführt.


Unverträglich
-------------

Eine Funktion oder Operation, die sich auch mit äußerer
Synchronisierung nicht für die gleichzeitige Verwendung sicher machen
lässt. Unverträglicher Code greift typischerweise unsynchronisiert auf
globalen Zustand zu und darf während der gesamten Laufzeit des
Programms nur aus einem einzigen Thread aufgerufen werden.

Beispiel: eine Funktion, die prozessweiten Zustand wie Signal-Handler
oder Umgebungsvariablen verändert, bei der gleichzeitige Aufrufe aus
beliebigen Threads selbst mit äußerer Sperre mit der Laufzeitumgebung
oder anderen Bibliotheken in Konflikt geraten können.


Verträglich
-----------

Eine Funktion oder Operation, die sich sicher aus mehreren Threads
aufrufen lässt, *vorausgesetzt*, der Aufrufer sorgt für geeignete
äußere Synchronisierung, etwa indem er für die Dauer jedes Aufrufs
eine *Sperre* hält. Ohne solche Synchronisierung können gleichzeitige
Aufrufe zu *Race-Conditions* oder *Datenkonflikten* führen.

Beispiel: eine Funktion, die aus einem Objekt liest oder in es
schreibt, dessen interner Zustand nicht durch eine Sperre geschützt
ist. Aufrufer müssen sicherstellen, dass niemals zwei Threads
gleichzeitig auf dasselbe Objekt zugreifen.


Sicher auf verschiedenen Objekten
---------------------------------

Eine Funktion oder Operation, die sich ohne äußere Synchronisierung
sicher aus mehreren Threads aufrufen lässt, solange jeder Thread auf
einem **anderen** Objekt arbeitet. Zwei Threads dürfen die Funktion
gleichzeitig aufrufen, sie dürfen ihr aber nicht dasselbe Objekt (oder
Objekte, die sich zugrunde liegenden Zustand teilen) als Argument
übergeben.

Beispiel: eine Funktion, die Felder eines Structs mit nicht atomaren
Schreibzugriffen verändert. Zwei Threads können die Funktion jeweils
sicher auf ihrer eigenen Struct-Instanz aufrufen, gleichzeitige
Aufrufe auf *derselben* Instanz brauchen aber äußere Synchronisierung.


Sicher auf gemeinsam genutzten Objekten
---------------------------------------

Eine Funktion oder Operation, die sich sicher gleichzeitig auf
**demselben** Objekt verwenden lässt. Die Umsetzung verwendet innere
Synchronisierung (etwa *objektspezifische Sperren* oder kritische
Abschnitte), um gemeinsam genutzten veränderbaren Zustand zu schützen;
Aufrufer müssen also nicht selbst sperren.

Beispiel: "PyList_GetItemRef()" lässt sich aus mehreren Threads auf
demselben "PyListObject" aufrufen – die Funktion verwendet innere
Synchronisierung, um die Zugriffe zu serialisieren.


Atomar
------

Eine Funktion oder Operation, die anderen Threads gegenüber *atomar*
erscheint – aus Sicht der anderen Threads läuft sie augenblicklich ab.
Das ist die stärkste Form der Threadsicherheit.

Beispiel: "PyMutex_IsLocked()" liest den Zustand des Mutex atomar und
lässt sich jederzeit aus jedem Thread aufrufen.


Threadsicherheit bei list-Objekten
==================================

Ein einzelnes Element aus einer "list" zu lesen ist *atomar*:

   lst[i]   # list.__getitem__

Die folgenden Methoden durchlaufen die Liste und verwenden *atomare*
Lesezugriffe auf jedes Element, um ihre Aufgabe zu erfüllen. Das
heißt, dass sie Ergebnisse zurückgeben können, die von gleichzeitigen
Änderungen beeinflusst sind:

   item in lst
   lst.index(item)
   lst.count(item)

Alle obigen Operationen vermeiden das Erwerben *objektspezifischer
Sperren*. Sie blockieren gleichzeitige Änderungen nicht. Andere
Operationen, die eine Sperre halten, verhindern nicht, dass diese hier
Zwischenzustände beobachten.

Alle weiteren Operationen von hier an blockieren über die
*objektspezifische Sperre*.

Ein einzelnes Element über "lst[i] = x" zu schreiben lässt sich sicher
aus mehreren Threads aufrufen und beschädigt die Liste nicht.

Die folgenden Operationen geben neue Objekte zurück und erscheinen
anderen Threads *atomar*:

   lst1 + lst2    # verkettet zwei Listen zu einer neuen Liste
   x * lst        # wiederholt lst x-mal in einer neuen Liste
   lst.copy()     # gibt eine flache Kopie der Liste zurück

Die folgenden Methoden, die nur auf einem einzelnen Element arbeiten
und kein Verschieben erfordern, sind *atomar*:

   lst.append(x)  # hängt am Ende der Liste an, kein Verschieben nötig
   lst.pop()      # entnimmt ein Element am Ende der Liste, kein Verschieben nötig

Die Methode "clear()" ist ebenfalls *atomar*. Andere Threads können
das Entfernen der Elemente nicht beobachten.

Die Methode "sort()" ist nicht *atomar*. Andere Threads können zwar
keine Zwischenzustände während des Sortierens beobachten, die Liste
erscheint aber für die Dauer des Sortierens leer.

Die folgenden Operationen können *sperrfreien* Operationen erlauben,
Zwischenzustände zu beobachten, denn sie verändern mehrere Elemente an
Ort und Stelle:

   lst.insert(idx, item)  # verschiebt Elemente
   lst.pop(idx)           # idx nicht am Ende der Liste, verschiebt Elemente
   lst *= x               # kopiert Elemente an Ort und Stelle

Die Methode "remove()" kann gleichzeitige Änderungen zulassen, weil
der Vergleich der Elemente beliebigen Python-Code ausführen kann (über
"__eq__()").

"extend()" lässt sich sicher aus mehreren Threads aufrufen.  Die
Garantien hängen allerdings vom übergebenen Iterable ab. Ist es eine
"list", ein "tuple", ein "set", ein "frozenset", ein "dict" oder ein
Dictionary-View-Objekt (nicht aber eine Unterklasse davon), dann ist
die "extend"-Operation vor gleichzeitigen Änderungen am Iterable
sicher. Andernfalls wird ein Iterator erzeugt, der von einem anderen
Thread gleichzeitig verändert werden kann. Dasselbe gilt für das
Verketten einer Liste mit anderen Iterables an Ort und Stelle über
"lst += iterable".

Ebenso lässt sich die Zuweisung an ein Listen-Slice über "lst[i:j] =
iterable" sicher aus mehreren Threads aufrufen, "iterable" wird aber
nur dann gesperrt, wenn es ebenfalls eine "list" ist (nicht aber eine
Unterklasse davon).

Operationen mit mehreren Zugriffen sowie das Iterieren sind nie
atomar. Zum Beispiel:

   # NICHT atomar: lesen, ändern, schreiben
   lst[i] = lst[i] + 1

   # NICHT atomar: prüfen, dann handeln
   if lst:
       item = lst.pop()

   # NICHT threadsicher: iterieren während des Änderns
   for item in lst:
       process(item)  # ein anderer Thread kann lst verändern

Zieh äußere Synchronisierung in Erwägung, wenn du "list"-Instanzen
über Threads hinweg gemeinsam nutzt.


Threadsicherheit bei dict-Objekten
==================================

Ein Dictionary mit dem Konstruktor "dict" zu erzeugen ist atomar, wenn
das Argument ein "dict" oder ein "tuple" ist. Bei der Methode
"dict.fromkeys()" ist das Erzeugen des Dictionarys atomar, wenn das
Argument ein "dict", "tuple", "set" oder "frozenset" ist.

Die folgenden Operationen und Funktionen sind *sperrfrei* und
*atomar*.

   d[key]       # dict.__getitem__
   d.get(key)   # dict.get
   key in d     # dict.__contains__
   len(d)       # dict.__len__

Alle weiteren Operationen von hier an halten die *objektspezifische
Sperre*.

Ein einzelnes Element zu schreiben oder zu entfernen lässt sich sicher
aus mehreren Threads aufrufen und beschädigt das Dictionary nicht:

   d[key] = value        # schreiben
   del d[key]            # löschen
   d.pop(key)            # entfernen und zurückgeben
   d.popitem()           # letzten Eintrag entfernen und zurückgeben
   d.setdefault(key, v)  # einfügen, falls nicht vorhanden

Diese Operationen können Schlüssel über "__eq__()" vergleichen, was
beliebigen Python-Code ausführen kann. Während solcher Vergleiche kann
das Dictionary von einem anderen Thread verändert werden. Bei
eingebauten Typen wie "str", "int" und "float", die "__eq__()" in C
umsetzen, wird die zugrunde liegende Sperre während der Vergleiche
nicht freigegeben. Dort stellt sich das Problem nicht.

Die folgenden Operationen geben neue Objekte zurück und halten die
*objektspezifische Sperre* für die Dauer der Operation:

   d.copy()      # gibt eine flache Kopie des Dictionarys zurück
   d | other     # führt zwei Dictionarys zu einem neuen zusammen
   d.keys()      # gibt ein neues dict_keys-View-Objekt zurück
   d.values()    # gibt ein neues dict_values-View-Objekt zurück
   d.items()     # gibt ein neues dict_items-View-Objekt zurück

Die Methode "clear()" hält die Sperre für ihre Dauer. Andere Threads
können das Entfernen der Elemente nicht beobachten.

Die folgenden Operationen sperren beide Dictionarys. Für "update()"
und "|=" gilt das nur, wenn der andere Operand ein "dict" ist, das den
üblichen dict-Iterator verwendet (nicht aber eine Unterklasse, die das
Iterieren überschreibt). Für den Gleichheitsvergleich gilt es für
"dict" und seine Unterklassen:

   d.update(other_dict)  # beide gesperrt, wenn other_dict ein dict ist
   d |= other_dict       # beide gesperrt, wenn other_dict ein dict ist
   d == other_dict       # beide gesperrt bei dict und Unterklassen

Alle Vergleichsoperationen vergleichen auch die Werte über "__eq__()".
Bei nicht eingebauten Typen kann die Sperre während des Vergleichs
daher freigegeben werden.

"fromkeys()" sperrt sowohl das neue Dictionary als auch das Iterable,
wenn das Iterable genau ein "dict", "set" oder "frozenset" ist (keine
Unterklasse):

   dict.fromkeys(a_dict)      # sperrt beide
   dict.fromkeys(a_set)       # sperrt beide
   dict.fromkeys(a_frozenset) # sperrt beide

Wird aus einem Iterable aktualisiert, das kein dict ist, wird nur das
Ziel-Dictionary gesperrt. Das Iterable kann von einem anderen Thread
gleichzeitig verändert werden:

   d.update(iterable)        # iterable ist kein dict: nur d gesperrt
   d |= iterable             # iterable ist kein dict: nur d gesperrt
   dict.fromkeys(iterable)   # iterable ist kein dict/set/frozenset: nur das Ergebnis gesperrt

Operationen mit mehreren Zugriffen sowie das Iterieren sind nie
atomar:

   # NICHT atomar: lesen, ändern, schreiben
   d[key] = d[key] + 1

   # NICHT atomar: prüfen, dann handeln (TOCTOU)
   if key in d:
       del d[key]

   # NICHT threadsicher: iterieren während des Änderns
   for key, value in d.items():
       process(key)  # ein anderer Thread kann d verändern

Um Probleme zwischen Prüfzeitpunkt und Nutzungszeitpunkt (time-of-
check to time-of-use, TOCTOU) zu vermeiden, verwende atomare
Operationen oder behandle Ausnahmen:

   # pop() mit Standardwert verwenden statt prüfen, dann löschen
   d.pop(key, None)

   # Oder die Ausnahme behandeln
   try:
       del d[key]
   except KeyError:
       pass

Um sicher über ein Dictionary zu iterieren, das von einem anderen
Thread verändert werden kann, iteriere über eine Kopie:

   # Eine Kopie anlegen, um sicher zu iterieren
   for key, value in d.copy().items():
       process(key)

Zieh äußere Synchronisierung in Erwägung, wenn du "dict"-Instanzen
über Threads hinweg gemeinsam nutzt.


Threadsicherheit bei set-Objekten
=================================

Die Funktion "len()" ist sperrfrei und *atomar*.

Die folgende Leseoperation ist sperrfrei. Sie blockiert gleichzeitige
Änderungen nicht und kann Zwischenzustände von Operationen beobachten,
die die objektspezifische Sperre halten:

   elem in s    # set.__contains__

Diese Operation kann Elemente über "__eq__()" vergleichen, was
beliebigen Python-Code ausführen kann. Während solcher Vergleiche kann
die Menge von einem anderen Thread verändert werden. Bei eingebauten
Typen wie "str", "int" und "float" gibt "__eq__()" die zugrunde
liegende Sperre während der Vergleiche nicht frei. Dort stellt sich
das Problem nicht.

Alle weiteren Operationen von hier an halten die objektspezifische
Sperre.

Ein einzelnes Element hinzuzufügen oder zu entfernen lässt sich sicher
aus mehreren Threads aufrufen und beschädigt die Menge nicht:

   s.add(elem)      # Element hinzufügen
   s.remove(elem)   # Element entfernen, Ausnahme auslösen, falls nicht vorhanden
   s.discard(elem)  # Element entfernen, falls vorhanden
   s.pop()          # ein beliebiges Element entfernen und zurückgeben

Diese Operationen vergleichen ebenfalls Elemente, es gelten also
dieselben Überlegungen zu "__eq__()" wie oben.

Die Methode "copy()" gibt ein neues Objekt zurück und hält die
objektspezifische Sperre für ihre Dauer, sodass sie immer atomar ist.

Die Methode "clear()" hält die Sperre für ihre Dauer. Andere Threads
können das Entfernen der Elemente nicht beobachten.

Die folgenden Operationen nehmen nur "set" oder "frozenset" als
Operanden und sperren immer beide Objekte:

   s |= other                   # other muss ein set/frozenset sein
   s &= other                   # other muss ein set/frozenset sein
   s -= other                   # other muss ein set/frozenset sein
   s ^= other                   # other muss ein set/frozenset sein
   s & other                    # other muss ein set/frozenset sein
   s | other                    # other muss ein set/frozenset sein
   s - other                    # other muss ein set/frozenset sein
   s ^ other                    # other muss ein set/frozenset sein

"set.update()", "set.union()", "set.intersection()" und
"set.difference()" können mehrere Iterables als Argumente nehmen. Sie
durchlaufen alle übergebenen Iterables und tun Folgendes:

   * "set.update()" and "set.union()" lock both objects only when the
     other operand is a "set", "frozenset", or "dict".

   * "set.intersection()" and "set.difference()" always try to lock
     all objects.

"set.symmetric_difference()" versucht, beide Objekte zu sperren.

Die aktualisierenden Varianten der obigen Methoden unterscheiden sich
untereinander ebenfalls in einigen Punkten:

   * "set.difference_update()" and "set.intersection_update()" try to
     lock all objects one-by-one.

   * "set.symmetric_difference_update()" only locks the arguments if
     it is of type "set", "frozenset", or "dict".

Die folgenden Methoden versuchen immer, beide Objekte zu sperren:

   s.isdisjoint(other)          # beide gesperrt
   s.issubset(other)            # beide gesperrt
   s.issuperset(other)          # beide gesperrt

Operationen mit mehreren Zugriffen sowie das Iterieren sind nie
atomar:

   # NICHT atomar: prüfen, dann handeln
   if elem in s:
         s.remove(elem)

   # NICHT threadsicher: iterieren während des Änderns
   for elem in s:
         process(elem)  # ein anderer Thread kann s verändern

Zieh äußere Synchronisierung in Erwägung, wenn du "set"-Instanzen über
Threads hinweg gemeinsam nutzt.  Mehr dazu unter Unterstützung von
freiem Threading in Python.


Threadsicherheit bei bytearray-Objekten
=======================================

   Die Funktion "len()" ist sperrfrei und *atomar*.

   Verkettung und Vergleiche verwenden das Puffer-Protokoll, das eine
   Größenänderung verhindert, aber die objektspezifische Sperre nicht
   hält. Diese Operationen können Zwischenzustände gleichzeitiger
   Änderungen beobachten:

      ba + other    # kann gleichzeitige Schreibzugriffe beobachten
      ba == other   # kann gleichzeitige Schreibzugriffe beobachten
      ba < other    # kann gleichzeitige Schreibzugriffe beobachten

   Alle weiteren Operationen von hier an halten die objektspezifische
   Sperre.

   Ein einzelnes Element oder ein Slice zu lesen lässt sich sicher aus
   mehreren Threads aufrufen:

      ba[i]        # bytearray.__getitem__
      ba[i:j]      # slice

   Die folgenden Operationen lassen sich sicher aus mehreren Threads
   aufrufen und beschädigen das bytearray nicht:

      ba[i] = x         # einzelnes Byte schreiben
      ba[i:j] = values  # Slice schreiben
      ba.append(x)      # einzelnes Byte anhängen
      ba.extend(other)  # mit einem Iterable erweitern
      ba.insert(i, x)   # einzelnes Byte einfügen
      ba.pop()          # letztes Byte entfernen und zurückgeben
      ba.pop(i)         # Byte am Index entfernen und zurückgeben
      ba.remove(x)      # erstes Vorkommen entfernen
      ba.reverse()      # an Ort und Stelle umkehren
      ba.clear()        # alle Bytes entfernen

   Die Zuweisung an ein Slice sperrt beide Objekte, wenn *values* ein
   "bytearray" ist:

      ba[i:j] = other_bytearray  # beide gesperrt

   Die folgenden Operationen geben neue Objekte zurück und halten die
   objektspezifische Sperre für ihre Dauer:

      ba.copy()     # gibt eine flache Kopie zurück
      ba * n        # in ein neues bytearray wiederholen

   Der Test auf Enthaltensein hält die Sperre für seine Dauer:

      x in ba       # bytearray.__contains__

   Alle anderen Methoden von bytearray (etwa "find()", "replace()",
   "split()", "decode()" und so weiter) halten die objektspezifische
   Sperre für ihre Dauer.

   Operationen mit mehreren Zugriffen sowie das Iterieren sind nie
   atomar:

      # NICHT atomar: prüfen, dann handeln
      if x in ba:
          ba.remove(x)

      # NICHT threadsicher: iterieren während des Änderns
      for byte in ba:
          process(byte)  # ein anderer Thread kann ba verändern

   Um sicher über ein bytearray zu iterieren, das von einem anderen
   Thread verändert werden kann, iteriere über eine Kopie:

      # Eine Kopie anlegen, um sicher zu iterieren
      for byte in ba.copy():
          process(byte)

   Zieh äußere Synchronisierung in Erwägung, wenn du
   "bytearray"-Instanzen über Threads hinweg gemeinsam nutzt.  Mehr
   dazu unter Unterstützung von freiem Threading in Python.


Threadsicherheit bei memoryview-Objekten
========================================

"memoryview"-Objekte bieten Zugriff auf die internen Daten eines
zugrunde liegenden Objekts, ohne zu kopieren. Die Threadsicherheit
hängt sowohl von der memoryview selbst als auch vom zugrunde liegenden
Puffer-Exporteur ab.

Die Umsetzung von memoryview verwendet im *Build mit freien Threads*
atomare Operationen, um ihre eigenen Exporte zu verfolgen. Eine
memoryview zu erzeugen und freizugeben ist threadsicher. Der
Attributzugriff (etwa "shape", "format") liest Felder, die für die
Lebensdauer der memoryview unveränderlich sind. Gleichzeitige
Lesezugriffe sind daher sicher, solange die memoryview nicht
freigegeben wurde.

Die eigentlichen Daten, auf die über die memoryview zugegriffen wird,
gehören allerdings dem zugrunde liegenden Objekt. Der gleichzeitige
Zugriff auf diese Daten ist nur dann sicher, wenn das zugrunde
liegende Objekt es zulässt:

* Bei unveränderlichen Objekten wie "bytes" sind gleichzeitige
  Lesezugriffe über mehrere memoryviews sicher.

* Bei veränderbaren Objekten wie "bytearray" ist es nicht sicher,
  denselben Speicherbereich aus mehreren Threads ohne äußere
  Synchronisierung zu lesen und zu schreiben. Das kann zu beschädigten
  Daten führen. Beachte, dass auch nur lesende memoryviews
  veränderbarer Objekte keine Datenkonflikte verhindern, wenn das
  zugrunde liegende Objekt von einem anderen Thread verändert wird.

   # NICHT sicher: gleichzeitige Schreibzugriffe auf denselben Puffer
   data = bytearray(1000)
   view = memoryview(data)
   # Thread 1: view[0:500] = b'x' * 500
   # Thread 2: view[0:500] = b'y' * 500

   # Sicher: eine Sperre für den gleichzeitigen Zugriff verwenden
   import threading
   lock = threading.Lock()
   data = bytearray(1000)
   view = memoryview(data)

   with lock:
       view[0:500] = b'x' * 500

Die Größe des zugrunde liegenden Objekts zu ändern oder es neu
zuzuweisen (etwa durch einen Aufruf von "bytearray.resize()"), während
eine memoryview exportiert ist, löst "BufferError" aus. Das wird
unabhängig von Threads durchgesetzt.
