PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Richtige Threads in PHP einfach erstellen mit pthreads

with 17 comments

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).

Written by Michael Kliewe

März 13th, 2013 at 12:26 pm

Posted in PHP

Tagged with , , , ,

17 Responses to 'Richtige Threads in PHP einfach erstellen mit pthreads'

Subscribe to comments with RSS or TrackBack to 'Richtige Threads in PHP einfach erstellen mit pthreads'.

  1. 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

  2. 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

  3. wie kann ich den die Extension (nachträglich) installieren.

    Florian Niefünd

    13 Mrz 13 at 18:39

  4. @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

  5. 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

  6. 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

  7. Java zu nutzen um gut skalieren zu können

    :D

    sonyon

    13 Mrz 13 at 19:26

  8. 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

  9. 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

  10. @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

  11. —————————
    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

  12. 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

    :D läuft
    dll habe ich in dem php verzeichnis gelegt :)

    Peter

    15 Mrz 13 at 21:44

  13. 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

  14. 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

  15. 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

  16. 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 :D

    Sergej

    30 Mrz 13 at 22:48

  17. 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

Leave a Reply

You can add images to your comment by clicking here.