PHP (Pseudo-) Threading

Bewertung [ Bewertung abgeben ] Artikel geschrieben am 25.03.2021 um 17:48 Uhr, aktualisiert am 22.07.2023, um 21:29 Uhr.

In der heutigen Zeit werden Anwendungen immer komplexer und bearbeiten immer größere Datenmengen, um ein erwartetes Ergebnis zu berechnen. Das Thema Nebenläufigkeit, umgangssprachlich meist Threading genannt, ist dabei eine zentrale Komponente. Früher wie heute ist das Threading ein Maßstab für gute Anwendungen, wobei der Begriff gut nicht unbedingt immer für die Qualität der Anwendung steht. Damals überzeugte eine Anwendung damit, dass eine aufwendige Berechnung als eigener Hintergrundprozess angestoßen wurde, die Anwendung selbst allerdings zu jeder Zeit weitere Eingaben verarbeiten konnte. Ein gutes Beispiel, das selbst noch heute in diese - schon fast triviale – Kategorie passt, ist ein E-Mail-Client, der regelmäßig einen Abgleich mit dem Mailserver vollzieht, um neue E-Mails laden zu können. (Moderne Techniken, wie die Push-Benachrichtigung, ignorieren wir an dieser Stelle mal großzügig!)

Seit nunmehr 10 Jahren geht die technische Entwicklung bei den Prozessoren nicht mehr den Weg der Einzelkern-Skalierung. Mit der Produktion der Mehrkern-Prozessoren begann für Anwendungen eine ganz neue Ära. Gerade zu Anfang gingen die normalen Benutzer davon aus, dass ein Rechner mit zwei Kernen doch wohl annähernd die doppelte Leistung bringen müsste.
Das eine Anwendung dafür optimiert sein muss und es durchaus auch Anwendungen gibt, die das nicht leisten können, wird gerne noch heute vergessen.

Man muss sich das wie einen Reifenwechsel am eigenen PKW vorstellen. Wenn man ihn alleine macht, benötigt man eine Stunde für alles, inklusive aufräumen. Lange Zeit waren Quad-Core-Prozessoren die wohl beliebtesten am Markt. Das würde drei Kumpels entsprechen, die am Wochenende vorbeikommen und beim Reifenwechsel mithelfen. An reiner Hardware könnte also jeder einen Reifen übernehmen und damit müsste sich (rein theoretisch) die Zeit auf lediglich 15 Minuten verringern. In der Praxis ist das wohl geringfügig anders, aber für die Darstellung soll uns das ausreichen, denn wenn nur ein Schraubenschlüssel vorhanden ist, dann muss immer gewartet werden, bis der vorherige Reifenwechsel abgeschlossen ist. Das kann man gut vergleichen mit Programmen, die nicht vollständig optimiert sind. Mit drei Kumpels wird es bereits allein durch die Anwesenheit eine kleine Steigerung der Effizient geben (Reifen holen, alten Reifen markieren und aufräumen, …), aber so richtig profitieren kann man erst, wenn der komplette Prozess an die neuen Gegebenheiten angepasst ist.

Zurück in die Welt von PHP. Tatsächlich besitzt PHP bereits intern über Bibliotheken, um Threading zu erzeugen. Der Nachteil: Keine davon ist plattformunabhängig und intuitiv zu nutzen, wie man es von anderen Sprachen gewöhnt ist. Eine lange Zeit wurde die Erweiterung pthreads entwickelt, die diese Lücke füllen sollte. Aufgrund der internen (und zum Teil drastischen) Änderungen des PHP-Kerns, wurde die Entwicklung inzwischen eingestellt (hier gibt es dazu einen Kommentar. Das Projekt wurde inzwischen archiviert, gleichzeitig hat der Autor die Entwicklung einer neuen Erweiterung parallel gestartet. Leider gibt es in den Fehlern zahlreiche Berichte über Probleme bei der Verwendung mit PHP 8 und die letzten Binarys für Windows sind für die PHP-Version 7.4.
Es war immer wieder in der Diskussion eine solche Implementierung nativ in PHP zu integrieren. Bis zum heutigen Tage ist das mehr Wunsch als Wirklichkeit.

Das bringt uns zur relevanten Frage: Brauchen wir eine native Variante?

Die Antwort ist so einfach, wie frustrierend zugleich, denn es kommt auf die Anwendung und die konkrete Aufgabe für die Nebenläufigkeit an! Zwei Beispiele:

  • Nehmen wir einen klassischen Newsletter-Versand. In der typischen Entwicklung hätte man eine E-Mail, die an eine Menge N an Empfängern gehen soll. Jede einzelne ist davon, abgesehen natürlich der Personenbezogenen Metadaten, vollkommen identisch. Gleichzeitig möchte man am Ende aber wissen, wie viele der E-Mails jetzt erfolgreich waren und welche nicht. Ein perfektes Beispiel des „richtigen“ Threadings! Jede E-Mail könnte im einfachsten Fall einen eigenen Prozess darstellen und dieser Prozess hat ein Ergebnis. Der Hauptprozess stößt alle Prozesse an und wartet, bis jeder seine Rückmeldung geliefert hat. Danach hat er eine Liste von allen positiven Empfängern und kann die negativen daraus extrahieren.
    Für die Profis: Ich möchte nicht verheimlichen, dass man genau diesen Prozess mit ein paar kniffen auch umwandeln kann zu einem Pseudo-Threading! In einem normalen Java-Programm würde man aber tatsächlich auf vollständige und normale Aufgaben (= Tasks) zurückgreifen, während der Hauptprozess auf die Ergebnisse abwartet. In der Webentwicklung ist vieles sehr ähnlich und trotzdem anders!
  • Ein typisches Beispiel für das Gegenteil stellt der Cronjob dar. Als Webentwickler wird man nicht drum herumkommen, davon zu hören. Dennoch möchte ich die Funktionsweise kurz erläutern. Die Anwendung Cronjob (eine Anwendung auf Linux/Unix-Systemen) definiert eine Liste von Anweisungen mit einem Regelwerk für jede dieser Anweisungen, wann genau diese ausgeführt werden soll. In den meisten Fällen werden andere Anwendungen gestartet (z.B. alte Log- oder Cache-Dateien entfernen) oder noch viel einfacher, Internetseiten aufgerufen. Viele CMS nutzen das, um regelmäßige Arbeiten durchzuführen, wie automatische Updates, Inhalte freigeben, die zu einem bestimmten Zeitpunkt erscheinen sollen und weiteres.
    Diese einzelnen Anweisungen werden schlicht gestartet und abgearbeitet. Wenn das Ergebnis der Anwendung egal ist oder gar an einer anderen Stelle geschrieben wird (was sehr oft der Fall ist), dann kann man davon ausgehen, dass der Prozess gestartet wird, aber theoretisch bis zum Ende nicht verfolgt werden müsste.

Am Ende des Artikels wird sich zeigen, dass viele Beispiele, mit wenigen Handgriffen, entsprechend für eine andere Verarbeitung umgebaut werden können. Mithilfe eines Datenspeichers und kleinen Erweiterungen der Datenmodelle können die klassischen Threading-Prozesse umgebaut werden, damit eine Verfolgung auch ohne direkte Prozesssteuerung möglich ist. Dafür muss die Definition zu einem konkreten Konzept umgewandelt werden.