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
😀 läuft
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
Hallo Michael! Nachdem ich mir zu diesem Thema die Finger wund gesucht habe, bin ich wieder auf deiner Homepage gelandet. Ich bin begeistert. Dass muss ich heute Abend unbedingt ausprobieren. Hoffe, mich diesmal intelligenter anzustellen. Gruß Thor
Thor
12 Jul 13 at 15:37
@Jan: Wenn du in der run() Methode eines Threads deine Logik implementierst, dann musst den den Autoloader dort nochmal erneut registrieren. Das liegt daran, das alles was du in der run() Methode aufrufst in einem eigenständigen Kontext läuft, sprich innerhalb der Methode ist nichts von dem bekannt was du aussen initialisiert hast. Die einzige Möglichkeit Daten an die Methode zu übergeben ist die Daten im Konstruktor zu übergeben und als Membervariable in der run() Methode verfügbar zu machen.
@Konstantin: Wenn du PHP nicht extra neu kompilieren willst, dann könntest du für einen Test die Runtime unseres appserver’s nutzen. Diese bringt eine komplette Laufzeitumgebung unter /opt/appserver mit, ohne an deinem System etwas zu verändern. Aktuell haben wir eine Version für DEBIAN Wheezy + Mac OS X 10.8.x und arbeiten gerade an einem RPM und einer Windows Version. Du kannst dir die Runtime unter http://www.appserver.io/downloads herunterladen, für Feedback sind wir natürlich dankbar.
@Wolfgang: Die kannst pthreads auch verwenden wenn du dein PHP Script über Apache oder nginx ausführst, hier gibt es keine Einschränkungen
LG
Tim Wagner
16 Okt 13 at 08:32
[…] findet ihr hier. Darüber hinaus kann ich euch noch einen schöne kurze Einführung dazu von PHP Gangsta empfehlen, sowie ein paar weitere Beispiele […]
Aufbau einer Multi-Core-fähigen PHP Version (pthreads) unter Ubuntu | Tom Thaler
7 Nov 13 at 11:22
Hallo,
ich habe auf meinem Windows Vista Rechner versucht PThreads zum laufen zu bekommen, leider ohne Erfolg. Wäre es möglich, dass ihr mich bei der Installation unterstützt?
PHP Warning: PHP Startup: Unable to load dynamic library ‚C:\php\php_pthreads.dll‘ – Das angegebene Modul wurde nicht gefunden.
in Unknown on line 0
PHP Fatal error: Class ‚Thread‘ not found in ….
Ich verwende PHP 5.4.21 sowie die oben erwähnten zugehörigen Extensions. Nachdem die php.ini_production nicht funktioniert hatte, habe ich die php.ini aus einer laufenden 5.xx XAMPP Installation verwendet. Leider mit dem selben Erfolg 🙁
Schon mal vielen Dank!
Marko
10 Nov 13 at 20:41
Werden die Threads auf mehrere Kerne aufgeteilt?
david
16 Dez 13 at 18:50
@david Das hängt auch vom Betriebssystem ab nehme ich an. Die pthread Extension ist eine „Posix Threads implementation“, und die werden meines Wissens nach bei modernen Betriebssystemen auf die Cores verteilt.
Aber Google weiß das bestimmt besser:
http://stackoverflow.com/questions/10089552/concurrency-of-posix-threads-in-multiprocessor-machine
http://www.linuxforums.org/forum/programming-scripting/198391-c-programming-binding-posix-threads-specific-processor-core.html
http://stackoverflow.com/questions/1407786/how-to-set-cpu-affinity-of-a-particular-pthread
Michael Kliewe
16 Dez 13 at 19:08
Hallo Michael!
Hier ist mal wieder der Thor.
Zum Multithreading-Konzept verwende ich deinen Artikel mit popen. Das funktioniert auch soweit ganz gut.
Heute bin ich aber über einen spitzen Stein gestolpert. 😉
Mittels cmd und popen unter Windows Server 2008 R2 starte ich parallel mehrere Aufrufe einer und derselben Klasse.
Darin befindet sich unter anderem „fsockopen(…)“ Bei http:// auf Port:80 funktioniert das Skript einwandfrei. Kommt es jedoch zum Aufruf von https:// Port:443 hagelt es Fehlermeldungen. Starte ich das Skript via Browser läuft alles glatt.
Kann es sein, dass auf CMD (Command-Line) es wegen Zertifikaten nicht funktioniert? Muss ich das über PHP selbst händeln?
Freue mich auf eine fundierte Aussage von dir.
Gruß Thor
Thor Duisenberg
7 Jan 14 at 20:51
@Thor Duisenberg: Und wie lauten die Fehlermeldungen?
Michael Kliewe
7 Jan 14 at 23:23
Geht wirklich sehr gut – danke!
Rouven
20 Feb 14 at 11:44
„Fatal error: Class ‚Thread‘ not found“…
Dieser Fehler wird ausgegeben wenn ich dein Beispiel oben einfach einbinde und auf meinen Server hochlade.
Muss ich da noch irgendwie mit „require_once“ oder so die pthreads library einbinden? Wenn ja wie?
Dom
7 Apr 14 at 02:01
Fehlt in dem Beispiel nicht ein wenig die Synchronisation, bzw. das Warten auf die Beendigung der Threads?
So wie es da steht, ist doch die Zeitmessung falsch.
Sebastian
17 Aug 15 at 11:46
Upps, habe nicht gut genug gelesen. Lösch am besten meine Kommentare einfach 🙂
Sebastian
17 Aug 15 at 11:47
Ab Version 3 von pthreads ist die Verwendung laut Autor hart auf die CLI-SAPI beschränkt. Die Verwendung über FPM oder Modul ist nicht mehr möglich. Näheres ist dazu in seinem Blog zu finden
– http://blog.krakjoe.ninja/2015/09/the-worth-of-advice.html
– http://blog.krakjoe.ninja/2014/10/but-is-it-web-scale.html.
Letztlich wird er wissen, warum. Ich hatte mit Threads auf eine Möglichkeit zur zeitlichen Beschränkung von blockierenden Befehlen gehofft, die eine solche Beschränkung nicht vorgesehen haben.
Martin Abraham
29 Dez 15 at 12:32
もっと錦上に花を添えるの来場者は、また感じる「Rolex(ロレックス)愛の中国」というメッセージ、そして有事での証。まず、Rolex(ロレックス)が中国で多額の投資。ロレックスコピー次に、それと競争相手と同じで、積極的に賛助中国スター、彼らを招待ブランド。を含む世界ランキング第五の中国网坛名将李娜(2011年フランスオープン女子シングルス優勝、そしてここから1位になって、グランドスラム大会に優勝したアジア選手)、そのチームメイト郑洁(かつて摘みのグランドスラムダブルス)、中国の順位の第1のゴルファーの梁文冲や、国際上で高い名声を有し中国人ピアニスト王羽佳とユンディ・リ。 http://www.bestevance.com/rolex/Cellini/index.htm
もっと錦上に花を添えるの来場者は、また感じる「Rolex(ロレックス)愛の中国」というメッセージ、そして有事での証。まず、Rolex(ロレックス)が中国で多額の投資。ロレックスコピー
7 Mrz 16 at 00:00
より高い終わり時計に適した、このように、ストラップサントーニの贅沢な靴屋によって作られています。これはいくつかの高品質の結果(臭い)が使用されている革。 シャネルスーパーコピー ステンレス鋼のモデルは、黒い革のバンドを特徴とします、赤い金の反復は、茶色の革のひもの上に来ます。あなたの黒い革のひもを得るならば、それはdeployantは、茶色のストラップが標準的な香りバックルを特徴としながら(レッドゴールドでではあるが)。 http://www.gowatchs.com/brand-215.html
より高い終わり時計に適した、このように、ストラップサントーニの贅沢な靴屋によって作られています。これはいくつかの高品質の結果(臭い)が使用されている革。 シャネルスーパー
7 Mrz 16 at 00:01
私にとっては、2015年年間カレンダーiwcの腕時計の最も印象的な要素の運動である。オメガスーパーコピー後に大44.2mmワイドケースと、広大なサファイアクリスタル窓を通して、あなたは近代的な機械の腕時計運動の自社製キャリバー52850 iwcの素晴らしい例であることを見ます。iwcの米国の運動のアーキテクチャの範囲内で種々の谷と山の深い見方を与えることは素晴らしい仕事をしました。と運動を取り上げた事例の多くは、大きなプラスである。 http://www.brandiwc.com/brand-1-copy-0.html
私にとっては、2015年年間カレンダーiwcの腕時計の最も印象的な要素の運動である。オメガスーパーコピー後に大44.2mmワイドケースと、広大なサファイアクリスタル窓を通して、あなたは近
7 Mrz 16 at 00:01
もしアリストテレスはまだ生きている、彼はきっとそう評価Blancpainブランパン:“抜群の良好な習慣を訓練としての芸術形式は、したがって、抜群のは1種の行為ではなくて、1種の習慣。」継2006年と2007年にそれぞれ出しCalibre 13R0とCalibre 1315 2項のムーブメントの後、ブランパンは自主開発に取り組んで時計ムーブメントCalibres 13R5、Calibre 66R9とCalibre 5025ムーブメントはCalibres 13R0を基礎にして、Blancpainを表現した新型腕時計開発高い品質ムーブメントの決意を固める。ブランドコピーその執着と信念は、新しいシリーズが全面的に体現L-Evolution。これも証明し、卓越しただけでは、不断の努力の成果。 http://www.ooobag.com/wallet/louisvuitton/index_14.html
もしアリストテレスはまだ生きている、彼はきっとそう評価Blancpainブランパン:“抜群の良好な習慣を訓練としての芸術形式は、したがって、抜群のは1種の行為ではなくて、1種の習慣。」
7 Mrz 16 at 00:01