Multithreading in Python mit Beispiel: Lernen Sie GIL in Python

⚡ Intelligente Zusammenfassung

Multithreading in Python Mehrere Threads werden innerhalb eines Prozesses ausgeführt, sodass sie sich den Speicher teilen und parallel arbeiten. Das Threading-Modul erstellt und verwaltet diese Threads, während die globale Interpretersperre (GIL) die echte Parallelität einschränkt. Daher eignet sich diese Technik am besten für Aufgaben mit hohem Ein-/Ausgabeaufwand.

  • 🔘 Thread vs. Prozess: Ein Thread ist eine Ausführungseinheit innerhalb eines Prozesses, und Threads teilen sich die Prozessdaten, während sie einzeln ausgeführt werden.
  • ☑️ Zwei Module: Das veraltete _thread-Modul bietet Low-Level-Aufrufe, während das übergeordnete Threading-Modul der moderne Standard für die Erstellung von Threads ist.
  • Threads erstellen: Erweitere die Thread-Klasse, überschreibe __init__ und run(), rufe dann start() zum Starten und join() zum Warten auf die Fertigstellung auf.
  • 🧪 Vermeiden Sie Rennsituationen: Eine Sperre lässt immer nur einen Thread gleichzeitig in einen kritischen Abschnitt eintreten und verhindert so Deadlocks und beschädigte gemeinsam genutzte Daten.
  • Das GIL verstehen: Die globale Interpretersperre (GIL) erlaubt es einem Thread, gleichzeitig zu laufen. Python Bytecode auf einmal verarbeiten, daher ist für rechenintensive Aufgaben Multiprocessing erforderlich.
  • 🤖 KI-Workloads: Multithreading beschleunigt eingabe-/ausgabegebundene KI-Schritte wie das Laden von Daten, während NumPy und PyTorch gibt den GIL für parallele Berechnungen frei.

Multithreading in Python

Das Python Die Programmiersprache ermöglicht die Nutzung von Multiprocessing oder Multithreading. In diesem Tutorial lernen Sie, wie man Multithread-Anwendungen schreibt. Python.

Was ist ein Faden?

Ein Thread ist eine Ausführungseinheit in der nebenläufigen Programmierung. Multithreading ist eine Technik, die es einer CPU ermöglicht, mehrere Aufgaben eines Prozesses gleichzeitig auszuführen. Diese Threads können unabhängig voneinander ausgeführt werden und teilen sich dabei die Prozessressourcen.

Was ist ein Prozess?

Ein Prozess ist im Grunde ein Programm, das gerade ausgeführt wird. Wenn Sie eine Anwendung auf Ihrem Computer starten (z. B. einen Browser oder einen Texteditor), erstellt das Betriebssystem einen solchen Prozess.

Was ist Multithreading in Python?

Multithreading in Python Multithreading ist eine bekannte Programmiertechnik, bei der mehrere Threads innerhalb eines Prozesses ihren Datenspeicher mit dem Hauptthread teilen. Dies ermöglicht eine einfache und effiziente Informationsweitergabe und Kommunikation zwischen den Threads. Threads sind ressourcenschonender als Prozesse. Mehrere Threads können unabhängig voneinander ausgeführt werden und teilen sich dabei die Prozessressourcen. Ziel des Multithreadings ist es, mehrere Aufgaben und Funktionen gleichzeitig auszuführen.

Was ist Multiprocessing?

Multiprozessing ermöglicht Ihnen, mehrere unabhängige Prozesse gleichzeitig auszuführen. Diese Prozesse teilen ihre Ressourcen nicht und kommunizieren über IPC.

Python Multithreading vs. Multiprocessing

Um Prozesse und Threads zu verstehen, betrachten Sie folgendes Szenario: Eine .exe-Datei auf Ihrem Computer ist ein Programm. Wenn Sie sie öffnen, lädt das Betriebssystem sie in den Arbeitsspeicher, und die CPU führt sie aus. Die Instanz des Programms, die nun ausgeführt wird, wird als Prozess bezeichnet.

Jeder Prozess besteht aus zwei grundlegenden Komponenten:

  • Der Code des Chamäleons
  • Die Fakten

Nun kann ein Prozess einen oder mehrere aufgerufene Unterteile enthalten Threads. Dies hängt von der Architektur des Betriebssystems ab. Man kann sich einen Thread als einen Teil eines Prozesses vorstellen, der vom Betriebssystem separat ausgeführt werden kann.

Anders ausgedrückt handelt es sich um einen Strom von Anweisungen, die vom Betriebssystem unabhängig ausgeführt werden können. Threads innerhalb eines einzelnen Prozesses teilen sich die Daten dieses Prozesses und sind so konzipiert, dass sie zusammenarbeiten, um Parallelverarbeitung zu ermöglichen.

Warum Multithreading verwenden?

Mit Multithreading können Sie eine Anwendung in mehrere Unteraufgaben aufteilen und diese Aufgaben gleichzeitig ausführen. Wenn Sie Multithreading richtig verwenden, können Sie die Geschwindigkeit, Leistung und Darstellung Ihrer Anwendung verbessern.

Python Multithreading

Python Unterstützt Konstrukte für Multiprocessing und Multithreading. In diesem Tutorial konzentrieren Sie sich hauptsächlich auf die Implementierung. Multithreading Anwendungen mit PythonEs gibt zwei Hauptmodule, die zur Verarbeitung von Threads verwendet werden können. Python:

  1. Das Faden Modul und
  2. Das einfädeln Modulen

Jedoch in PythonEs gibt auch etwas, das man Global Interpreter Lock (GIL) nennt. Es ermöglicht keine wesentlichen Leistungssteigerungen und kann sogar Veteran die Leistung einiger Multithread-Anwendungen. Alles darüber erfahren Sie in den nächsten Abschnitten dieses Tutorials.

Die Thread- und Threading-Module

Die beiden Module, die Sie in diesem Tutorial kennenlernen werden, sind: Thread-Modul und der Threading-Modul.

Das Thread-Modul ist jedoch schon lange veraltet. Beginnend mit Python 3, es wurde als veraltet gekennzeichnet und ist nur zugänglich als _Faden für Abwärtskompatibilität.

Sie sollten die höhere Ebene verwenden einfädeln Modul für Anwendungen, die Sie bereitstellen möchten. Das Thread-Modul wurde hier nur zu Schulungszwecken behandelt.

Das Thread-Modul

Die Syntax zum Erstellen eines neuen Threads mit diesem Modul lautet wie folgt:

thread.start_new_thread(function_name, arguments)

Okay, jetzt haben Sie die grundlegende Theorie behandelt, um mit dem Codieren zu beginnen. Also, öffnen Sie Ihr IDLE oder einen Notizblock und geben Sie Folgendes ein:

import time
import _thread

def thread_test(name, wait):
   i = 0
   while i <= 3:
      time.sleep(wait)
      print("Running %s\n" %name)
      i = i + 1

   print("%s has finished execution" %name)

if __name__ == "__main__":
    
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

Speichern Sie die Datei und drücken Sie F5, um das Programm auszuführen. Wenn alles richtig gemacht wurde, sollten Sie folgende Ausgabe sehen:

Das Thread-Modul

In den folgenden Abschnitten erfahren Sie mehr über Rennbedingungen und wie Sie damit umgehen können.

Das Thread-Modul

CODE-ERKLÄRUNG

  1. Diese Anweisungen importieren das Zeit- und Thread-Modul, das zur Steuerung der Ausführung und Verzögerung von Prozessen verwendet wird. Python Threads.
  2. Hier haben Sie eine Funktion namens definiert thread_test, welches von der aufgerufen wird start_new_thread Die Funktion führt eine while-Schleife viermal aus und gibt den Namen des aufrufenden Threads aus. Nach Abschluss der Schleife wird eine Meldung ausgegeben, die besagt, dass der Thread die Ausführung beendet hat.
  3. Dies ist der Hauptabschnitt Ihres Programms. Hier rufen Sie einfach die an start_new_thread Methode mit der thread_test Sie können die Funktion als Argument übergeben. Dadurch wird ein neuer Thread für die übergebene Funktion erstellt und deren Ausführung gestartet. Beachten Sie, dass Sie `thread_test` durch eine beliebige andere Funktion ersetzen können, die Sie als Thread ausführen möchten.

Das Threading-Modul

Dieses Modul ist die High-Level-Implementierung von Threads in Python und ist der De-facto-Standard für die Verwaltung von Multithread-Anwendungen. Es bietet im Vergleich zum Thread-Modul eine Vielzahl von Funktionen.

Struktur des Threading-Moduls

Struktur des Threading-Moduls

Hier ist eine Liste einiger nützlicher Funktionen, die in diesem Modul definiert sind:

Funktionsname Beschreibung
activeCount() Gibt die Anzahl von zurück Thread Objekte, die noch leben.
aktuellerThread() Gibt das aktuelle Objekt der Thread-Klasse zurück.
aufzählen() Listet alle aktiven Thread-Objekte auf.
isDaemon() Gibt true zurück, wenn der Thread ein Daemon ist.
ist am Leben() Gibt true zurück, wenn der Thread noch aktiv ist.
Methoden der Thread-Klasse
Start() Startet die Aktivität eines Threads. Es darf für jeden Thread nur einmal aufgerufen werden, da es bei mehrmaligem Aufruf einen Laufzeitfehler auslöst.
Lauf() Diese Methode bezeichnet die Aktivität eines Threads und kann von einer Klasse überschrieben werden, die die Thread-Klasse erweitert.
beitreten() Es blockiert die Ausführung anderen Codes, bis der Thread, für den die Methode join() aufgerufen wurde, beendet wird.

Hintergrundgeschichte: Die Thread-Klasse

Bevor Sie mit der Programmierung von Multithread-Programmen mithilfe des Threading-Moduls beginnen, ist es unerlässlich, die Thread-Klasse zu verstehen. Die Thread-Klasse ist die primäre Klasse, die die Vorlage und die Operationen eines Threads definiert. Python.

Die gebräuchlichste Methode zur Erstellung eines Multithread-Systems Python Die Anwendung besteht darin, eine Klasse zu deklarieren, die die Klasse Thread erweitert und deren run()-Methode überschreibt.

Zusammenfassend stellt die Thread-Klasse eine Codesequenz dar, die separat ausgeführt wird Faden der Kontrolle.

Wenn Sie eine Multithread-App schreiben, gehen Sie also wie folgt vor:

  1. Definiere eine Klasse, die die Thread-Klasse erweitert.
  2. Überschreiben Sie die __init__ Konstruktor
  3. Überschreiben Sie die Lauf() Methode

Sobald ein Thread-Objekt erstellt wurde, wird das Start() Mit dieser Methode kann die Ausführung dieser Aktivität gestartet werden, und die beitreten() Die Methode kann verwendet werden, um den gesamten anderen Code zu blockieren, bis die aktuelle Aktivität beendet ist.

Nun versuchen wir, Ihr vorheriges Beispiel mithilfe des Threading-Moduls umzusetzen. Starten Sie erneut Ihr Programm. IDLE und geben Sie Folgendes ein:

import time
import threading

class threadtester (threading.Thread):
    def __init__(self, id, name, i):
       threading.Thread.__init__(self)
       self.id = id
       self.name = name
       self.i = i
       
    def run(self):
       thread_test(self.name, self.i, 5)
       print ("%s has finished execution " %self.name)

def thread_test(name, wait, i):

    while i:
       time.sleep(wait)
       print ("Running %s \n" %name)
       i = i - 1

if __name__=="__main__":
    thread1 = threadtester(1, "First Thread", 1)
    thread2 = threadtester(2, "Second Thread", 2)
    thread3 = threadtester(3, "Third Thread", 3)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()

Dies wird die Ausgabe sein, wenn Sie den obigen Code ausführen:

Hintergrundgeschichte: Die Thread-Klasse

CODE-ERKLÄRUNG

Hintergrundgeschichte: Die Thread-Klasse

  1. Dieser Abschnitt entspricht unserem vorherigen Beispiel. Hier importieren Sie das Modul `time` und `thread`, die zur Steuerung der Ausführung und der Verzögerungen verwendet werden. Python Threads.
  2. In diesem Teil erstellen Sie eine Klasse namens Threadtester, die die Klasse erbt oder erweitert Thread Klasse des Threading-Moduls. Dies ist eine der gebräuchlichsten Methoden, um Threads zu erstellen in PythonSie sollten jedoch nur den Konstruktor und die Lauf() Methode in Ihrer App. Wie Sie im obigen Codebeispiel sehen können, ist die __init__ Methode (Konstruktor) wurde überschrieben. Ebenso haben Sie auch das überschrieben Lauf() Methode. Es enthält den Code, den Sie innerhalb eines Threads ausführen möchten. In diesem Beispiel haben Sie die Funktion thread_test() aufgerufen.
  3. Dies ist die Methode thread_test(), die den Wert von i Als Argument wird i in jeder Iteration um 1 verringert, und der restliche Code wird so lange ausgeführt, bis i gleich 0 wird. In jeder Iteration wird der Name des aktuell ausgeführten Threads ausgegeben und für die angegebene Anzahl von Sekunden gewartet (die ebenfalls als Argument übergeben wird).
  4. thread1 = threadtester(1, „First Thread“, 1) Hier erstellen wir einen Thread und übergeben die drei Parameter, die wir in __init__ deklariert haben. Der erste Parameter ist die ID des Threads, der zweite Parameter ist der Name des Threads und der dritte Parameter ist der Zähler, der bestimmt, wie oft die while-Schleife ausgeführt werden soll.
  5. thread2.start() Die Methode start() dient zum Starten der Ausführung eines Threads. Intern ruft die Funktion start() die Methode run() Ihrer Klasse auf.
  6. thread3.join() Die Methode join() blockiert die Ausführung anderen Codes und wartet, bis der Thread, in dem sie aufgerufen wurde, beendet ist.

Wie Sie bereits wissen, haben die Threads innerhalb desselben Prozesses Zugriff auf den Speicher und die Daten dieses Prozesses. Wenn also mehrere Threads gleichzeitig versuchen, die Daten zu ändern oder darauf zuzugreifen, können Fehler auftreten.

Im nächsten Abschnitt sehen Sie die verschiedenen Arten von Komplikationen, die auftreten können, wenn Threads auf Daten und den kritischen Abschnitt zugreifen, ohne vorher auf bestehende Zugriffstransaktionen zu prüfen.

Deadlocks und Race Conditions

Bevor wir uns mit Deadlocks und Race Conditions befassen, ist es hilfreich, einige grundlegende Definitionen im Zusammenhang mit der nebenläufigen Programmierung zu verstehen:

  • Kritischer Abschnitt: Es handelt sich um ein Codefragment, das auf gemeinsam genutzte Variablen zugreift oder diese verändert und als atomare Transaktion ausgeführt werden muss.
  • Kontextwechsel: Es handelt sich um den Prozess, den eine CPU durchführt, um den Zustand eines Threads zu speichern, bevor sie von einer Aufgabe zur anderen wechselt, damit er später an derselben Stelle fortgesetzt werden kann.

Blockaden

Blockaden sind das am meisten gefürchtete Problem, mit dem Entwickler beim Schreiben von nebenläufigen/multithreadfähigen Anwendungen konfrontiert werden. PythonAm besten lassen sich Deadlocks anhand des klassischen Informatikbeispiels verstehen, das als Deadlock bekannt ist. Restaurants PhiloSophers Problem.

Die Problemstellung für speisende Philosophen lautet wie folgt:

Fünf Philosophen sitzen an einem runden Tisch mit fünf Tellern Spaghetti (einer Nudelsorte) und fünf Gabeln, wie in der Abbildung dargestellt.

Restaurants PhiloSophers Problem

Restaurants PhiloSophers Problem

Ein Philosoph muss zu jeder Zeit entweder essen oder nachdenken.

Darüber hinaus muss ein Philosoph die beiden Gabeln neben ihm nehmen (also die linke und die rechte Gabel), bevor er die Spaghetti essen kann. Das Deadlock-Problem tritt auf, wenn alle fünf Philosophen gleichzeitig ihre rechte Gabel nehmen.

Da jeder der Philosophen eine Gabel hat, werden sie alle warten, bis die anderen ihre Gabeln hinlegen. Infolgedessen wird keiner von ihnen Spaghetti essen können.

Ähnlich verhält es sich in einem parallelen System: Ein Deadlock tritt auf, wenn verschiedene Threads oder Prozesse (Philosophen) gleichzeitig versuchen, die gemeinsam genutzten Systemressourcen (Forks) zu beanspruchen. Dies führt dazu, dass keiner der Prozesse ausgeführt werden kann, da sie auf eine andere Ressource warten, die von einem anderen Prozess gehalten wird.

Rennbedingungen

Eine Race Condition ist ein unerwünschter Programmzustand, der auftritt, wenn ein System zwei oder mehr Operationen gleichzeitig ausführt. Betrachten wir beispielsweise diese einfache for-Schleife:

i=0; # a global variable
for x in range(100):
    print(i)
    i+=1;

Wenn Sie erstellen n Bei der Anzahl der Threads, die diesen Code gleichzeitig ausführen, lässt sich der Wert von `i` (der von den Threads gemeinsam genutzt wird) nach Programmende nicht bestimmen. Dies liegt daran, dass sich Threads in einer realen Multithreading-Umgebung überschneiden können und der von einem Thread abgerufene und geänderte Wert von `i` sich ändern kann, während ein anderer Thread darauf zugreift.

Dies sind die beiden Hauptklassen von Problemen, die in einem Multithread- oder verteilten System auftreten können. Python Anwendung. Im nächsten Abschnitt erfahren Sie, wie Sie dieses Problem durch die Synchronisierung von Threads beheben können.

Syncchronisierende Threads

Um mit Race Conditions, Deadlocks und anderen Thread-basierten Problemen umzugehen, bietet das Threading-Modul die Zahnscheiben Objekt. Die Idee ist, dass ein Thread, wenn er auf eine bestimmte Ressource zugreifen möchte, eine Sperre für diese Ressource erhält. Sobald ein Thread eine bestimmte Ressource sperrt, kann kein anderer Thread darauf zugreifen, bis die Sperre aufgehoben wird. Dadurch werden die Änderungen an der Ressource atomar und Race Conditions werden vermieden.

Eine Sperre ist ein Synchronisierungsprimitiv auf niedriger Ebene, das von der _Faden Modul. Ein Schloss kann sich zu jedem Zeitpunkt in einem von zwei Zuständen befinden: verschlossen or entsperrt. Es unterstützt zwei Methoden:

  1. erwerben(): Wenn der Sperrzustand aufgehoben ist, ändert der Aufruf der Methode `acquire()` den Zustand in gesperrt und gibt die Funktion zurück. Befindet sich der Zustand jedoch im gesperrten Zustand, wird der Aufruf von `acquire()` blockiert, bis die Methode `release()` von einem anderen Thread aufgerufen wird.
  2. freigeben(): Mit der Methode release() wird der Zustand auf „unlocked“ gesetzt, also eine Sperre aufgehoben. Es kann von jedem Thread aufgerufen werden, nicht unbedingt von dem Thread, der die Sperre erhalten hat.

Hier ist ein Beispiel für die Verwendung von Sperren in Ihren Apps. Starten Sie Ihre IDLE und geben Sie Folgendes ein:

import threading
lock = threading.Lock()

def first_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the first funcion')
        lock.release()

def second_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the second funcion')
        lock.release()

if __name__=="__main__":
    thread_one = threading.Thread(target=first_function)
    thread_two = threading.Thread(target=second_function)

    thread_one.start()
    thread_two.start()

    thread_one.join()
    thread_two.join()

Drücken Sie nun F5. Sie sollten eine Ausgabe wie diese sehen:

Syncchronisierende Threads

CODE-ERKLÄRUNG

Syncchronisierende Threads

  1. Hier erstellen Sie einfach eine neue Sperre, indem Sie die aufrufen threading.Lock() Werksfunktion. Intern gibt Lock() eine Instanz der effektivsten konkreten Lock-Klasse zurück, die von der Plattform verwaltet wird.
  2. In der ersten Anweisung erhalten Sie die Sperre, indem Sie die Methode acquire() aufrufen. Wenn die Sperre erteilt wurde, drucken Sie „Sperre erworben“ zur Konsole. Sobald die Ausführung des gesamten Codes, den der Thread ausführen soll, abgeschlossen ist, heben Sie die Sperre auf, indem Sie die Methode release() aufrufen.

Die Theorie ist gut und schön, aber woher wissen Sie, dass die Sperre tatsächlich funktioniert hat? Wenn Sie sich die Ausgabe ansehen, werden Sie feststellen, dass jede print-Anweisung genau eine Zeile nach der anderen ausgibt. Erinnern Sie sich, dass in einem früheren Beispiel die Ausgaben von print ungeordnet waren, da mehrere Threads gleichzeitig auf die print()-Methode zugegriffen haben. Hier wird die print-Funktion erst aufgerufen, nachdem die Sperre erworben wurde. Daher werden die Ausgaben nacheinander und zeilenweise angezeigt.

Abgesehen von Schlössern, Python Unterstützt außerdem einige andere Mechanismen zur Handhabung der Thread-Synchronisierung, wie unten aufgeführt:

  1. RLocks
  2. Semaphores
  3. Erkrankungen
  4. Veranstaltungen und
  5. Barriers

Global Interpreter Lock (und wie man damit umgeht)

Bevor wir auf die Einzelheiten eingehen PythonLassen Sie uns, GIL, einige Begriffe definieren, die zum Verständnis des folgenden Abschnitts hilfreich sein werden:

  1. CPU-gebundener Code: Dies bezieht sich auf jeden Codeabschnitt, der direkt von der CPU ausgeführt wird.
  2. E/A-gebundener Code: Dies kann jeder Code sein, der über das Betriebssystem auf das Dateisystem zugreift.
  3. CPython: es ist die Referenz Implementierung of Python und kann als der in C geschriebene Interpreter beschrieben werden und Python (Programmiersprache).

Was ist GIL in Python?

Globale Dolmetschersperre (GIL) in Python Der GIL (Global Interpreter Lock) ist eine Prozesssperre oder ein Mutex, der bei der Prozessverwaltung verwendet wird. Er stellt sicher, dass jeweils nur ein Thread auf eine bestimmte Ressource zugreifen kann und verhindert die gleichzeitige Verwendung von Objekten und Bytecode. Dies führt zu einer Leistungssteigerung bei Single-Thread-Programmen. Python ist sehr einfach und leicht umzusetzen.

Mit einer Sperre kann sichergestellt werden, dass zu einem bestimmten Zeitpunkt nur ein Thread Zugriff auf eine bestimmte Ressource hat.

Eines der Merkmale von Python Das liegt daran, dass es eine globale Sperre für jeden Interpreterprozess verwendet, was bedeutet, dass jeder Prozess die Python Der Interpreter selbst als Ressource.

Nehmen wir beispielsweise an, Sie haben Folgendes geschrieben: Python Ein Programm, das zwei Threads für CPU- und E/A-Operationen nutzt. Folgendes passiert bei der Ausführung dieses Programms:

  1. Das Python Der Interpreter erstellt einen neuen Prozess und erzeugt die Threads.
  2. Wenn Thread-1 mit der Ausführung beginnt, erfasst er zunächst die GIL und sperrt sie.
  3. Wenn Thread-2 jetzt ausgeführt werden möchte, muss er auf die Freigabe der GIL warten, auch wenn ein anderer Prozessor frei ist.
  4. Nehmen wir nun an, dass Thread 1 auf eine E/A-Operation wartet. Zu diesem Zeitpunkt gibt er den GIL frei und Thread 2 holt ihn sich.
  5. Wenn Thread-1 nach Abschluss der E/A-Vorgänge jetzt ausgeführt werden möchte, muss er erneut auf die Freigabe der GIL durch Thread-2 warten.

Aus diesem Grund kann jeweils nur ein Thread auf den Interpreter zugreifen, was bedeutet, dass immer nur ein Thread ausgeführt wird. Python Code zu einem bestimmten Zeitpunkt.

Bei einem Einkernprozessor ist dies unproblematisch, da die Threads mithilfe von Time Slicing (siehe erster Abschnitt dieses Tutorials) verwaltet werden. Bei Mehrkernprozessoren hingegen beeinträchtigt eine rechenintensive Funktion, die auf mehreren Threads ausgeführt wird, die Effizienz des Programms erheblich, da nicht alle verfügbaren Kerne gleichzeitig genutzt werden.

Warum wurde GIL benötigt?

Die CPython Der Garbage Collector verwendet eine effiziente Speicherverwaltungstechnik, die als Referenzzählung bekannt ist. So funktioniert sie: Jedes Objekt im Speicher Python Jedes Objekt besitzt einen Referenzzähler, der erhöht wird, wenn es einer neuen Variablen zugewiesen oder einem Container (wie Tupeln, Listen usw.) hinzugefügt wird. Umgekehrt verringert sich der Referenzzähler, wenn die Referenz ihren Gültigkeitsbereich verlässt oder die `del`-Anweisung aufgerufen wird. Erreicht der Referenzzähler eines Objekts den Wert 0, wird es vom Garbage Collector entfernt und der belegte Speicher freigegeben.

Das Problem besteht jedoch darin, dass die Referenzzählervariable wie jede andere globale Variable anfällig für Race Conditions ist. Um dieses Problem zu lösen, haben die Entwickler von Python Wir entschieden uns für die Verwendung der globalen Interpretersperre. Die Alternative wäre gewesen, jedem Objekt eine Sperre hinzuzufügen, was zu Deadlocks und einem erhöhten Aufwand durch acquire()- und release()-Aufrufe geführt hätte.

Daher stellt GIL eine erhebliche Einschränkung für Multithreading dar. Python Programme, die rechenintensive Operationen ausführen (und dadurch effektiv Single-Threaded sind). Wenn Sie mehrere CPU-Kerne in Ihrer Anwendung nutzen möchten, verwenden Sie die Mehrfachverarbeitung Modul statt.

Häufig gestellte Fragen

Verwenden Sie Threading für E/A-intensive Aufgaben wie Netzwerkaufrufe, Dateizugriffe oder Datenbankabfragen, bei denen Threads auf externe Ressourcen warten. Nutzen Sie Multiprocessing für CPU-intensive Aufgaben wie rechenintensive Vorgänge, da es auf mehreren Kernen ausgeführt wird und den GIL umgeht.

Nicht standardmäßig. PEP 703 führte eine optionale, freithreadfähige Version ein. Python 3.13, das den GIL deaktiviert, und Python Version 3.14 setzt dies fort. Standard-Builds enthalten weiterhin den GIL, sodass die meisten Programme weiterhin genau wie zuvor laufen.

Ein Daemon-Thread läuft im Hintergrund und blockiert das Beenden des Programms nicht. Wenn nur noch Daemon-Threads vorhanden sind, Python Schließt sie und beendet das Programm. Setzen Sie einen Thread mit `thread.daemon = True`, bevor Sie `start()` aufrufen.

Die Klasse `Thread` gibt das Ergebnis von `run()` nicht direkt zurück. Speichern Sie den Wert im Thread-Objekt oder einer gemeinsam genutzten Warteschlange oder verwenden Sie `concurrent.futures.ThreadPoolExecutor`, dessen `submit()`-Methode ein Future-Objekt zurückgibt, das Sie mit `result()` auslesen können.

ThreadPoolExecutor ist eine Hilfsfunktion auf hoher Ebene, die einen Pool von Worker-Threads für Sie verwaltet. Sie übermitteln Aufgaben mit submit() oder map(), und die Funktion kümmert sich um die Erstellung, Wiederverwendung und Bereinigung der Threads und gibt Future-Objekte zurück, die jeweils ein Ergebnis enthalten.

Sie können Hunderte oder Tausende von Threads erstellen, aber der GIL lässt nur einen einzigen Thread ausführen. Python Bytecode zu jedem Zeitpunkt. Sie überlappen sich auch während E/A-Wartezeiten, daher helfen viele parallele Threads E/A-intensiven Programmen weitaus mehr als CPU-intensiven.

Ja, hauptsächlich für E/A-intensive Schritte wie das Laden von Daten, das Aufrufen von APIs oder das Lesen von Dateien. Aufwändiges Training ist CPU-lastig, daher werden Bibliotheken wie NumPy und Py verwendet.TorEntweder man gibt den GIL im C-Code frei oder man setzt auf Multiprocessing für echten Parallelismus.

Ja. GitHub Copilot vervollständigt häufige Muster wie Thread-Unterklassen, die Verwendung von Sperren und die Einrichtung von ThreadPoolExecutor anhand eines Kommentars oder Funktionsnamens. Überprüfen Sie den generierten Code immer auf Race Conditions und korrekte Sperrbehandlung, bevor Sie ihn verwenden.

Fassen Sie diesen Beitrag mit folgenden Worten zusammen: