Mehrere Aufgaben gleichzeitig auszuführen scheint eine unleugbare Quelle der Einsparung bei der Ausführung von Programmen zu sein. Python bietet dafür eine Lösung, die als "Threading" bezeichnet wird. Threading ist jedoch komplexer, als es auf den ersten Blick scheint, und seine Beherrschung erfordert eine gründliche Analyse der von einem Programm erwarteten Funktionen. Außerdem führt der Threading-Mechanismus von Python nicht zu einer effektiven Parallelisierung der verschiedenen Prozesse. Weitere Erklärungen folgen.
Angenommen, du hast eine Reihe von Aufgaben zu erledigen:
- Einen Vortrag für ein bevorstehendes Treffen verfassen.
- Drei potenzielle Kunden anrufen.
- Angebote verfassen.
Du kannst diese Aufgaben nacheinander erledigen. Du kannst dich aber auch auf dein Team verlassen und die Aktivitäten so verteilen, dass sie parallel erledigt werden. Es versteht sich von selbst, dass das Ganze dann schneller erledigt ist.
Bei der Programmierung mit Python funktioniert das „Threading“ nach einer ähnlichen Logik. Anstatt eine einzige Reihe von Anweisungen auszuführen, kann ein Programm mehrere „Python Threads“ verwalten, d. h. unabhängige Prozesse, die mehr oder weniger parallel laufen. Das Ergebnis ist in der Regel ein insgesamt effizienteres Programm.
In Wirklichkeit ist es aber nicht so einfach, denn es kann zu Koordinationsproblemen kommen. Nehmen wir das oben genannte Beispiel.
Eine Detailfrage in einem der Angebote könnte zum Teil von dem Gespräch mit einem der Interessenten abhängen. Daher kann das Angebot erst nach Abschluss des Gesprächs fertiggestellt werden. Solche Situationen werden auch im IT-Bereich vorkommen.
Daher ist es notwendig, die Python Threads gut zu organisieren und ihren Fortschritt zu koordinieren.
Das Python-Threading erfordert also eine vorherige Analyse, um idealerweise voneinander unabhängige Aufgaben zu isolieren und, falls dies nicht möglich ist, ihre Ausführung zeitlich zu koordinieren.
Was ist ein Thread?
Ein Python Thread ist ein separater Ausführungsablauf innerhalb eines Programms.
Wenn du ein Python-Skript startest, startest du standardmäßig eine Instanz des Python-Interpreters. Dein Code wird daher normalerweise in einem einzelnen Thread ausgeführt.
Es ist jedoch möglich, ein Python-Programm so zu gestalten, dass es bestimmte Aufgaben nebeneinander ausführt. In diesem Zusammenhang haben wir es dann mit mehreren Threads zu tun.
Ein Python-Prozess, der das „Threading“ ausnutzt, wird beendet, wenn alle Threads abgeschlossen sind.
💡Auch interessant:
Unter welchen Umständen sollte man Threading nutzen?
Hier sind einige Beispiele für Situationen, in denen Threading willkommen wäre.
- Eine Anwendung benötigt unabhängige Daten von mehreren Webseiten. Ohne Threading müsste sie warten, bis der Download von einer Website abgeschlossen ist, bevor sie die nächste starten könnte. Mit Threading kann sie diese Downloads gleichzeitig starten – sobald bei einem Download eine Wartezeit entsteht, übergibt sie die Leitung an einen anderen.
- Eine Anwendung ist so konzipiert, dass die vom Benutzer eingegebenen Informationen regelmäßig gespeichert werden. Wenn das Threading nicht genutzt wird, scheint die Anwendung beim Speichern der Daten „einzufrieren“. Wenn das Speichern durch einen separaten Thread erfolgt, wird die Anwendung weiterhin auf die Eingaben des Nutzers während des Speicherns reagieren.
- Sobald ein Nutzer bestimmte Informationen eingegeben hat, z. B. die Parameter eines potenziellen Darlehens, muss eine Anwendung eine komplexe Datenanalyse durchführen, die viel Rechenzeit in Anspruch nimmt.
- Wenn du diese Operationen an einen separaten Thread delegierst, wird die Anwendung weiterhin die Interaktionen des Benutzers akzeptieren.
Die Vorteile des Threadings
Verbesserung der Leistung
Die Gesamtausführungszeit eines Programms wird beschleunigt, vor allem, wenn ein Programm potenzielle Wartezeiten erfordert, wie beim Lesen einer Datei und bei Webanfragen.
Bessere Reaktion der App
Die Anwendung kann weiterhin auf Benutzeranfragen (wie Mausklicks) reagieren, während sie im Hintergrund andere Aufgaben erledigt.
Besserer Umgang mit festgefahrenen Situationen
Operationen, die die Ausführung des Codes blockieren könnten (wie das Warten auf eine Antwort des Netzwerks), führen nicht zu einer blockierenden Situation: Der Rest des Programms läuft normal weiter.
Ein klareres Design
Ein Programm, das in mehrere Threads aufgeteilt ist, hat in der Regel eine klarere Struktur. Ein Entwickler, der in den Code eingreifen muss, kann die Logik leichter entschlüsseln.
Die Nachteile von Multithreading
Potenziell komplexe Synchronisation
Wenn mehrere Threads die gleichen Daten ändern können, ist es entscheidend, dass die Koordination kontrolliert gestaltet wird, was zu einer gewissen Komplexität der Entwicklung führen kann.
Schwierige Fehlersuche
Threading kann zu Fehlern führen, die schwer zu erkennen sind, da sie von der genauen Reihenfolge abhängen, in der die Threads ausgeführt wurden.
Deadlock-Risiken
Ein Deadlock kann entstehen, wenn zwei Threads jeweils darauf warten, dass der andere eine bestimmte Ressource freigibt.
Multithreading vs multiprocessing
Wenn ein Programm mehrere Threads enthält, scheinen zwei oder mehr Aktionen parallel zu laufen. In Wirklichkeit ist dies bei Python nur eine Illusion.
GIL
Es gibt eine Situation in Python, die als GIL (Global Interpreter Lock) bezeichnet wird. Dies ist ein Mechanismus in Python, der dafür sorgt, dass zu einem bestimmten Zeitpunkt nur ein Thread ausgeführt wird.
Warum ein solcher Mechanismus? GIL wurde zu einer Zeit eingeführt, als Multi-Core-Prozessoren noch nicht weit verbreitet waren. Nun teilen sich aber alle Threads denselben Speicher. GIL sorgte also dafür, dass eine Datenstruktur nur von einem Thread gleichzeitig geändert werden konnte. Dieser Mechanismus sorgte für Sicherheit bei der Speicherverwaltung, schränkte aber die Fähigkeit von Python ein, Threads wirklich parallel auf mehreren Prozessorkernen auszuführen.
Nur ein Thread gleichzeitig
Im Zusammenhang mit Python spricht man eher von „Konkurrenz“ als von „Parallelität“. Was ist der Unterschied?
Im Kontext der Konkurrenz werden zu einem bestimmten Zeitpunkt eine oder mehrere Anweisungen eines bestimmten Threads aktiv sein. Mit anderen Worten: Jeder Thread wird zu einem bestimmten Zeitpunkt alleine arbeiten, und je nach den Umständen wird dem einen oder anderen Thread Priorität eingeräumt. Auf diese Weise schreitet der Code jedes Threads stetig voran und es entsteht der Eindruck einer parallelen Ausführung.
Im Kontext der Parallelität werden tatsächlich mehrere Prozesse gleichzeitig ausgeführt. Und jeder dieser Prozesse hat seinen eigenen Speicher.
Daher unterscheidet sich Multithreading von Multiprocessing.
Was lässt sich daraus schließen? Dass Threading für unabhängige Aufgaben mit Wartezeiten (z. B. ein Klick des Benutzers) effektiv ist und Multiprocessing besser geeignet ist, wenn rechenintensive Aufgaben parallel laufen können.