Richtige Threads in PHP einfach erstellen mit pthreads
Wir alle haben gelernt dass PHP kein richtiges Multi-Threading kann, und auch Kindprozesse zu forken ist nicht ganz trivial. Extensions wie PCNTL funktionieren nicht unter Windows und sind schwer zu bedienen. Man kann sich eventuell mit exec() behelfen und damit weitere Prozesse starten, verliert dann jedoch die Möglichkeit, die Prozesse zu synchronisieren oder einfach Nachrichten zwischen ihnen auszutauschen.
Und wer möchte eigentlich Threads in PHP und wofür?
Größere Projekte gehen heutzutage häufig den Weg, für Asynchronität und Parallelität Tools wie Gearman zu nutzen. Mit einem Gearman-Job-Server und einer beliebigen Menge an verbundenen Workern, die auch noch in beliebigen Programmiersprachen geschrieben sein können, kann man eine Menge Arbeit parallelisieren, aber ist eine komplette Gearman-Installation wirklich immer nötig? Gerade für kleinere Projekte ist das sicherlich zuviel, und auf Shared Hosting Platformen kann man keine Worker starten.
Die Sprache zu wechseln und .Net oder Java zu nutzen um gut skalieren zu können ist auch übertrieben, also was machen wir? Wir schauen uns mal die PHP-Extension pthreads an!
pthread, Einsatzgebiete, Beispiel
pthreads bringt endlich richtiges Multithreading. pthreads gibt es auch für Windows. Die erste Version via PECL wurde im September 2012 veröffentlicht. pthreads ist sehr einfach zu nutzen. Nehmen wir uns folgendes Beispielscript. Die Operation-Klasse muss einige langwierige Aufgaben erledigen, beispielsweise eine große Menge E-Mails versenden, lang laufende Datenbankabfragen machen, Dateien hoch- oder runterladen, mehrere Vorschaubilder in verschiedenen Größen erstellen, irgendwas was “lang” dauert.
<?php
class Operation
{
public function __construct($threadId)
{
$this->threadId = $threadId;
}
public function run()
{
printf("T %s: Sleeping 3sec\n", $this->threadId);
sleep(3);
printf("T %s: Hello World\n", $this->threadId);
}
}
$start = microtime(true);
for ($i = 1; $i <= 5; $i++) {
$t[$i] = new Operation($i);
$t[$i]->run();
}
echo microtime(true) - $start . "\n";
echo "end\n";
Die Ausgabe sieht wie erwartet so aus:
>php normal.php T 1: Sleeping 3sec T 1: Hello World T 2: Sleeping 3sec T 2: Hello World T 3: Sleeping 3sec T 3: Hello World T 4: Sleeping 3sec T 4: Hello World T 5: Sleeping 3sec T 5: Hello World 15.011883020401 end
Es werden in einer Schleife 5 Aufrufe gemacht, in denen jeweils ein sleep(3) drin steht. Das Script führt die Aufgaben nacheinander aus, nach 15 Sekunden ist das Ende erreicht.
Ändern wir nun das Script leicht ab:
<?php
class AsyncOperation extends Thread
{
public function __construct($threadId)
{
$this->threadId = $threadId;
}
public function run()
{
printf("T %s: Sleeping 3sec\n", $this->threadId);
sleep(3);
printf("T %s: Hello World\n", $this->threadId);
}
}
$start = microtime(true);
for ($i = 1; $i <= 5; $i++) {
$t[$i] = new AsyncOperation($i);
$t[$i]->start();
}
echo microtime(true) - $start . "\n";
echo "end\n";
Die einzigen Änderungen die wir gemacht haben sind die folgenden: Die Klasse erweitert nun die Klasse Thread, und es wird start() statt run() aufgerufen. Der Klassenname wurde angepasst. Wie sieht sie Ausgabe nun aus?
>php pthreads.php 0.041301012039185 end T 1: Sleeping 3sec T 2: Sleeping 3sec T 3: Sleeping 3sec T 4: Sleeping 3sec T 5: Sleeping 3sec T 1: Hello World T 2: Hello World T 3: Hello World T 4: Hello World T 5: Hello World
Die letzte Zeile des Scripts wird nach 0.04 Sekunden erreicht, und erst danach starten die Threads mit ihren Ausgaben. Nahezu gleichzeitig schreiben alle Threads dass sie sich nun 3 Sekunden schlafen legen, und nach 3 Sekunden schreiben alle 5 Threads gleichzeitig das “Hello World”. Das ganze PHP-Script endet nach knapp über 3 Sekunden.
Geht es noch einfacher? Ich glaube nein.
Für ausgeklügeltere Szenarien, wenn man einige Threads startet und dann warten muss bis alle ihr Ergebnis zusammengetragen haben, kann man die Threads synchronisieren, auf einen Thread warten bis er fertig ist, ein Thread kann warten bis er eine Benachrichtigung vom Hauptprozess bekommt und so weiter. Es gibt auch eine Mutex-Umsetzung und Conditions sowie Locking in den Threads, es ist also vieles möglich, würde hier aber den Rahmen eines Einführungsartikels sprengen.
Wenn ihr also Scripte habt die größere Aufgaben erledigen und sich der Einsatz von Gearman bisher nicht gelohnt hat, vielleicht ist pthread eine einfache Lösung. Einzige Voraussetzung ist die Installation der Extension und PHP 5.3 (das ihr hoffentlich alle bereits nutzt).



Mir bisher unbekannt. Wirklich eine äußerst geniale Extension. Danke für diesen Hinweis! Ersetzt natürlich keinen Cronjob, aber eben solche Szenarien, wie von dir geschildert, sind quasi prädestiniert für den Einsatz von pthreads.
Sebastian
13 Mrz 13 at 17:05
Nice article, thanks for the kind words
Just to be precise: pthreads supports 5.3+ ( up to and including the 5.5 alphas currently being released )
krakjoe
13 Mrz 13 at 17:48
wie kann ich den die Extension (nachträglich) installieren.
Florian Niefünd
13 Mrz 13 at 18:39
@Florian: Unter Windows musst du einfach nur die beiden Dateien in den ext/-Ordner packen und in der php.ini die Extension laden:
extension = pthreads.dll
Via PECL installieren geht wie bei jedem anderen PECL Modul auch:
pecl install pthreads
PHP muss thread-safe kompiliert worden sein, siehe Hinweis:
“To enable pthreads support, configure PHP with –enable-maintainer-zts and –enable-pthreads.”
Je nachdem welche Umgebung du hast und woher du dein PHP bezogen hast ist es also unterschiedlich kompliziert.
Michael Kliewe
13 Mrz 13 at 19:09
das habe ich vergessen zu erwählen
Ich habe ein Ubuntu root
Ich habe php einfach über apt installiert (apt-get install php5-curl php5-gd … usw.)
Florian Niefünd
13 Mrz 13 at 19:12
Dann würde ich es versuchen mit
sudo pecl install pthreads-beta
und wenn das funktioniert hat, noch die extension laden in der passenden php.ini (bei Apache das restarten nicht vergessen).
Michael Kliewe
13 Mrz 13 at 19:14
Java zu nutzen um gut skalieren zu können
sonyon
13 Mrz 13 at 19:26
Die Extension sieht wirklich super aus, aber irgendwie versteht sie sich bei mir nicht mit spl_autoload_register. Wenn ich die Threads starte, findet er einfach die Klassen nicht, die ich in der run-Methode benutzen möchte. Hat da jemand einen Tipp?
Jan
14 Mrz 13 at 08:54
Was hats denn mit der $t{$i} Schreibweise auf sich? Ich dachte geschweifte Klammern sind da, um explizit String-Offsets anzusprechen.
Marcel Anacker
14 Mrz 13 at 10:22
@Marcel Du hast Recht, da hab ich wohl die falschen Klammern erwischt. Anfangs hatte ich da einfach nur $t stehen, aber dann wurden die Threads interessanterweise nicht parallel abgearbeitet sondern nacheinander. Deshalb mußte ich da unterschiedliche Variablen nehmen.
Es funktioniert so zwar (interessanterweise), aber besser wäre natürlich
$t[$i]
oder
${‘t’.$i}
Michael Kliewe
14 Mrz 13 at 13:15
—————————
php.exe – Systemfehler
—————————
Das Programm kann nicht gestartet werden, da pthreadVC2.dll auf dem Computer fehlt. Installieren Sie das Programm erneut, um das Problem zu beheben.
Wohin muss ich diese dll tuen?
Peter
15 Mrz 13 at 21:13
T 1: Sleeping 3sec
T 2: Sleeping 3sec
T 3: Sleeping 3sec
T 4: Sleeping 3sec
T 5: Sleeping 3sec
0.029031991958618
end
T 1: Hello World
T 2: Hello World
T 3: Hello World
T 4: Hello World
T 5: Hello World
dll habe ich in dem php verzeichnis gelegt
Peter
15 Mrz 13 at 21:44
Hi,
leider gibt es keine Möglichkeit diese extension wirklich produktiv einzusetzen, da man sie nur installieren kann, wenn man php selbst kompiliert, da man php-zts benötigt. Oder hat jemand zufällig eine Möglichkeit gefunden?
Ich würde die extension gerne unter php-cli benutzen, d.h. ich bräuchte php-zts eigentlich gar nicht. Aber offensichtlich geht es nicht ohne und leider gibt es, zumindest unter centos, php-zts erst als Version 5.3.3 und nicht für 5.4.
Konstantin
16 Mrz 13 at 01:02
Gerade http://pkgs.org/centos-6-rhel-6/webtatic-i386/php54w-zts-5.4.12-1.w6.i386.rpm.html gefunden und lokal installiert. Leider sagt pecl install pthreads immer noch, dass kein ZTS enabled wäre. Noch jemand ne Idee?
Konstantin
16 Mrz 13 at 09:10
That RPM is incomplete, it does not contain a build environment. When you execute pecl it attempts to use your old build environment. Additionally, that RPM does not come with cli, it only contains the apache module version of PHP.
You would be better off building yourself, here’s some help with that:
http://pthreads.org/building
You can find me on #php.pecl on Efnet, if you would like assistance.
Joe Watkins
18 Mrz 13 at 11:26
Besten Dank dafür!! Das erspart mir die ganze Arbeit mit Gearman und ich kann endlich MultiProcessing auf meiner Windows Kiste haben. Davon hab ich lang geträumt
Sergej
30 Mrz 13 at 22:48
Seh ich das richtig, daß pthreads nur im CLI-Mode ausgeführt werden kann und nicht über Webserver ?
Wolfgang
9 Mai 13 at 21:31