Maison > développement back-end > tutoriel php > Programmation parallèle avec Pthreads en PHP - The Fundamentals

Programmation parallèle avec Pthreads en PHP - The Fundamentals

Jennifer Aniston
Libérer: 2025-02-10 08:57:09
original
647 Les gens l'ont consulté

Parallel Programming with Pthreads in PHP - the Fundamentals

Points clés

  • Évitez d'utiliser des pthreads dans des environnements de serveurs Web: En raison de problèmes de sécurité et d'évolutivité, les PTHreads ne doivent pas être utilisés dans des environnements de serveurs Web tels que FCGI, car il ne peut pas gérer efficacement plusieurs environnements dans ces environnements.
  • Utilisez des pthreads pour les tâches ponctuelles ou les opérations de liaison IO: pour les tâches qui effectuent un ou nécessitent un grand nombre d'opérations IO, l'utilisation de Pthreads peut aider à désinstaller le fil d'exécution principal et à le gérer dans une séparation Thread Ces opérations sont utilisées pour améliorer les performances.
  • Recycler les threads pour optimiser les ressources: Créer un nouveau thread pour chaque tâche peut prendre beaucoup de ressources;
  • Comprendre l'invariance des pthreads et la classe volatile: Par défaut, les propriétés des objets qui s'étendent en filetage sont immuables pour éviter la dégradation des performances, et la classe volatile fournit un moyen de gérer les mutables si nécessaire. des attributs.
  • Implémentez la synchronisation pour la sécurité des threads: pour empêcher la corruption des données et assurer des résultats cohérents lorsque plusieurs threads accèdent à des ressources partagées, utilisez les méthodes de synchronisation fournies par Pthreads, telles que les blocs de synchronisation et filetés :: attendre et filed: : notifier et d'autres méthodes.

Cet article a été examiné par Christopher Pitt. Merci à tous les évaluateurs de pairs SitePoint pour avoir rendu le contenu SitePoint Perfect!


Les développeurs PHP semblent rarement profiter du parallélisme. La simplicité de la programmation synchrone et unique est vraiment attrayante, mais parfois, utiliser un peu de concurrence peut entraîner des améliorations de performance intéressantes.

Dans cet article, nous apprendrons à implémenter des threads en PHP à l'aide de l'extension Pthreads. Cela nécessite l'installation de la version ZTS (Zend Thread-Safe) de PHP 7.x, ainsi que l'installation de Pthreads v3. (Au moment de la rédaction du moment de la rédaction, les utilisateurs de PHP 7.1 doivent installer à partir de la branche maître du repo Pthreads - voir une partie de cet article pour plus d'informations sur la construction d'extensions tierces de Source.)

Une explication rapide: Pthreads V2 est destiné à PHP 5.x, n'est plus pris en charge; Pthreads V3 est destiné à PHP 7.x et est en cours de développement actif.

Parallel Programming with Pthreads in PHP - the Fundamentals

Merci beaucoup à Joe Watkins (créateur de l'extension Pthreads) pour la relecture et aidé à améliorer mon message!

Lors de ne pas utiliser de pthreads

Avant de continuer, je veux d'abord expliquer la situation où vous ne devriez pas (et ne peut pas ) utiliser l'extension pthreads.

Dans Pthreads v2, il n'est pas recommandé d'utiliser des pthreads dans un environnement de serveur Web (c'est-à-dire dans un processus FCGI). En commençant par Pthreads V3, cette suggestion est appliquée, vous ne pouvez donc pas du tout l'utiliser dans un environnement de serveur Web. Deux principales raisons de le faire sont:

  1. Il n'est pas sûr d'utiliser plusieurs threads dans cet environnement (ce qui peut entraîner des problèmes d'IO et d'autres problèmes).
  2. Il ne s'allonge pas bien. Par exemple, supposons que vous ayez un script PHP qui crée un nouveau fil pour gérer un peu de travail, et ce script s'exécute chaque fois que vous le demandez. Cela signifie que pour chaque demande, votre application crée un nouveau thread (il s'agit d'un modèle de thread 1: 1 - un thread correspond à une demande). Si votre demande traite 1 000 demandes par seconde, elle crée 1 000 threads par seconde! L'exécution de nombreux threads sur une seule machine le submergera rapidement, et ce problème ne fera que s'aggraver à mesure que le taux de demande augmente.

C'est pourquoi les threads ne sont pas une bonne solution dans cet environnement. Si vous recherchez une solution pour les threads en tant que tâches de blocage IO (comme effectuer des demandes HTTP), permettez-moi de vous indiquer la direction de programmation asynchrone , qui peut être réalisée via des frameworks tels que AMP. SitePoint a publié d'excellents articles sur ce sujet (tels que l'écriture de bibliothèques asynchrones et la modification de Minecraft avec PHP), si vous êtes intéressé.

Revenons au point, passons directement au sujet!

Formation des tâches uniques

Parfois, vous voulez gérer les tâches uniques de manière multithread (comme effectuer des tâches liées à l'EM). Dans ce cas, vous pouvez utiliser la classe de threads pour créer un nouveau thread et exécuter certaines unités de travail dans ce thread séparé.

Exemple:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
Copier après la connexion
Copier après la connexion
Copier après la connexion

ci-dessus, la méthode d'exécution est l'unité de travail que nous exécuterons dans un nouveau thread. Lorsque Thread :: Démarrer est appelé, un nouveau thread est généré et la méthode d'exécution est appelée. Nous rejoignons ensuite le thread généré sur le thread principal (via Thread :: Join), qui bloquera jusqu'à ce que le thread individuel termine l'exécution. Cela garantit que la tâche a terminé l'exécution avant de tenter de produire le résultat (stocké dans $ task- & gt; réponse).

Les responsabilités de contaminer la classe avec une logique liée au thread (y compris avoir à définir des méthodes de course) peuvent ne pas être idéales. Nous pouvons isoler ces classes en les faisant étendre la classe filetée, puis les exécuter dans d'autres threads:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
Copier après la connexion
Copier après la connexion
Copier après la connexion

Toute classe qui doit être exécutée dans un thread séparé doit étendre la classe filetée d'une manière ou d'une autre. En effet, il fournit les capacités nécessaires pour exécuter dans différents threads, ainsi que pour fournir une sécurité implicite et des interfaces utiles (pour la synchronisation des ressources, etc.).

Comprenons rapidement la hiérarchie des classes exposée par des pthreads:

<code>Threaded (implements Traversable, Collectable)
    Thread
        Worker
    Volatile
Pool</code>
Copier après la connexion
Copier après la connexion
Nous avons déjà appris les bases du fil et des classes filetées, alors regardons les trois autres (travailleur, volatile et pool).

Recycler le fil

Il est coûteux de démarrer un nouveau fil pour que chaque tâche soit parallélisée. En effet, pour implémenter des threads à l'intérieur de PHP, les PThreads doivent adopter une architecture sans état partagée. Cela signifie que l'intégralité du contexte d'exécution (y compris chaque classe, interface, attribut et fonction) de l'instance actuelle de l'interpréteur PHP doit être copiée pour chaque thread créé. Comme cela peut avoir un impact significatif sur les performances, les threads doivent toujours être réutilisés autant que possible. Il existe deux façons de réutiliser les threads: l'utilisation du travailleur ou l'utilisation de pool.

La classe de travailleurs est utilisée pour effectuer une série de tâches de manière synchrone dans un autre fil. Cela se fait en créant une nouvelle instance de travail (cela créera un nouveau thread), puis en empilant des tâches sur ce thread séparé (via Worker :: Stack).

Ceci est un exemple simple:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
Copier après la connexion
Copier après la connexion
Copier après la connexion

Sortie:

Parallel Programming with Pthreads in PHP - the Fundamentals

Les piles ci-dessus pilent 15 tâches sur le nouvel objet $ Worker via Worker :: Stack, puis traitez-les dans l'ordre d'empilement. Comme indiqué ci-dessus, la méthode Worker :: Collect est utilisée pour nettoyer les tâches une fois la tâche terminée l'exécution. En l'utilisant dans la boucle while, nous bloquons le thread principal jusqu'à ce que toutes les tâches empilées aient été exécutées et ont été nettoyées, puis nous déclenchons Worker :: Arrêt. La fermeture du travailleur prématurément (c'est-à-dire, alors que les tâches doivent encore être exécutées) bloqueront toujours le fil principal jusqu'à ce que toutes les tâches soient terminées - les tâches ne seront tout simplement pas collectées à la poubelle (provoquant des fuites de mémoire).

La classe

La classe des travailleurs fournit d'autres méthodes liées à la pile de tâches, y compris le travailleur :: Débartière pour supprimer les éléments de pile les plus anciens, et Worker :: Getstacké pour exécuter le nombre d'éléments sur la pile. La pile d'un travailleur n'enregistre que les tâches à exécuter. Une fois la tâche de la pile exécutée, elle est supprimée et placée sur une autre pile (interne) pour la collecte des ordures (utilisant Worker :: Collect).

Une autre façon de réutiliser les threads lors de l'exécution de nombreuses tâches est d'utiliser des pools de threads (via la classe de piscine). Un pool de threads est entraîné par un ensemble de travailleurs pour permettre l'exécution de tâches simultanément, où le facteur de concurrence (le nombre de threads que le pool exécute) est spécifié au moment de la création de piscine. Ajustez l'exemple ci-dessus pour utiliser le bassin de travailleurs:

Sortie:
class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
Copier après la connexion
Copier après la connexion
Copier après la connexion

Parallel Programming with Pthreads in PHP - the Fundamentals Il existe des différences significatives entre l'utilisation des pools et l'utilisation de programmes de travailleurs. Premièrement, les piscines n'ont pas besoin d'être démarrées manuellement, elles commencent à exécuter des tâches dès qu'elles sont disponibles. Deuxièmement, nous soumettons les tâches

à la piscine au lieu de les empiler. De plus, la classe de pool ne s'étend pas en file d'attente, il peut donc ne pas être transmis à d'autres threads (contrairement au travailleur).

En tant que bonne pratique, vous devez toujours collecter des tâches pour les programmes et les piscines des travailleurs après avoir terminé les tâches et les fermer manuellement. Les threads créés via la classe de threads doivent également être rejoints au thread Creator.

pthreads et (non) variabilité

La dernière classe à présenter est volatile - un nouvel ajout à Pthreads v3. L'invariance est devenue un concept important dans les pthreads car sans elle, les performances seront gravement dégradées. Par conséquent, par défaut, les propriétés de la classe filetée qui sont elle-même un objet fileté sont désormais immuables et ne peuvent donc pas être réaffectées après l'attribution initiale. Maintenant, il est plus enclin à muter explicitement ces propriétés, et cela peut encore être fait en utilisant la nouvelle classe volatile.

Regardons rapidement un exemple pour démontrer la nouvelle contrainte d'invariance:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
Copier après la connexion
Copier après la connexion
Copier après la connexion

En revanche, la propriété filetée de la classe volatile est mutable:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous pouvons voir que la classe volatile remplace l'invariance appliquée par sa classe de classe parent de classe filetée pour permettre la réallocation (et l'incapacité) la propriété filetée.

sur la variabilité et les classes volatiles, il existe un autre dernier sujet de base qui doit être introduit - des tableaux. Lorsqu'un tableau est attribué à une propriété de la classe filetée, le tableau de Pthreads est automatiquement coulé à un objet volatil. En effet, il n'est pas sûr de manipuler les tableaux à partir de plusieurs contextes de PHP.

Regardons rapidement un exemple pour mieux comprendre:

<code>Threaded (implements Traversable, Collectable)
    Thread
        Worker
    Volatile
Pool</code>
Copier après la connexion
Copier après la connexion

Nous pouvons voir que les objets volatils peuvent être traités comme des tableaux, car ils apportent un support pour les opérateurs de sous-ensemble ([]) pour les opérations basées sur une table (comme indiqué ci-dessus). Cependant, la classe volatile n'est pas prise en charge par des fonctions basées sur un tableau communes telles que Array_Pop et Array_Shift. Au lieu de cela, la classe filetée nous fournit ces opérations en tant que méthodes intégrées.

comme démonstration:

class Task extends Threaded
{
    private $value;

    public function __construct(int $i)
    {
        $this->value = $i;
    }

    public function run()
    {
        usleep(250000);
        echo "Task: {$this->value}\n";
    }
}

$worker = new Worker();
$worker->start();

for ($i = 0; $i < 15; $i++) {
    $worker->stack(new Task($i));
}

while ($worker->collect());

$worker->shutdown();
Copier après la connexion

D'autres opérations prises en charge incluent Threed :: Chunk et Thineed :: Merge.

Synchronisation

Le dernier sujet qui sera introduit dans cet article est la synchronisation dans Pthreads. La synchronisation est une technologie qui permet un accès au contrôle aux ressources partagées.

par exemple, implémentons un compteur simple:

class Task extends Threaded
{
    private $value;

    public function __construct(int $i)
    {
        $this->value = $i;
    }

    public function run()
    {
        usleep(250000);
        echo "Task: {$this->value}\n";
    }
}

$pool = new Pool(4);

for ($i = 0; $i < 15; $i++) {
    $pool->submit(new Task($i));
}

while ($pool->collect());

$pool->shutdown();
Copier après la connexion

Si la synchronisation n'est pas utilisée, la sortie n'est pas déterministe. Plusieurs threads écrivent sur une seule variable sans contrôler l'accès peuvent entraîner des mises à jour perdues.

Corrigeons cela en ajoutant une synchronisation afin que nous obtenions la bonne sortie 20:

class Task extends Threaded // a Threaded class
{
    public function __construct()
    {
        $this->data = new Threaded();
        // $this->data is not overwritable, since it is a Threaded property of a Threaded class
    }
}

$task = new class(new Task()) extends Thread { // a Threaded class, since Thread extends Threaded
    public function __construct($tm)
    {
        $this->threadedMember = $tm;
        var_dump($this->threadedMember->data); // object(Threaded)#3 (0) {}
        $this->threadedMember = new StdClass(); // invalid, since the property is a Threaded member of a Threaded class
    }
};
Copier après la connexion

Les blocs de code de synchronisation peuvent également fonctionner avec Threed :: Wait and Threaked :: Notify (et ThreadEd :: NotifyOne).

Voici les incréments entrelacés de deux boucles synchrones:

class Task extends Volatile
{
    public function __construct()
    {
        $this->data = new Threaded();
        $this->data = new StdClass(); // valid, since we are in a volatile class
    }
}

$task = new class(new Task()) extends Thread {
    public function __construct($vm)
    {
        $this->volatileMember = $vm;

        var_dump($this->volatileMember->data); // object(stdClass)#4 (0) {}

        // still invalid, since Volatile extends Threaded, so the property is still a Threaded member of a Threaded class
        $this->volatileMember = new StdClass();
    }
};
Copier après la connexion

Vous avez peut-être remarqué que des conditions supplémentaires sont ajoutées autour de l'appel à fileter :: attendre. Ces conditions sont essentielles car elles permettent uniquement aux rappels synchrones de récupérer lorsque la notification et spécifie que la condition est vraie. Ceci est important car les notifications peuvent provenir de l'extérieur de l'appel ThreadEd :: Notifier. Par conséquent, si l'appel à Threed :: Wait n'est pas inclus dans la condition, nous serons vulnérables au faux réveil Faux , ce qui entraînera imprévisible le code.

Conclusion

Nous avons vu cinq classes (filets, threads, travailleurs, volatils et pool) qui sont livrés avec des pthreads, notamment en introduisant l'utilisation de chaque classe. Nous avons également examiné le nouveau concept d'invariance dans Pthreads, ainsi qu'un aperçu des fonctionnalités de synchronisation qu'il prend en charge. Avec ces bases couvertes, nous pouvons maintenant commencer à envisager d'appliquer des pthreads à certains cas d'utilisation pratiques! Ce sera le sujet de notre prochain post.

En même temps, si vous avez des idées d'application pour PTHEADS, n'hésitez pas à laisser vos réflexions dans la section des commentaires ci-dessous!

FAQ (FAQ) sur la programmation parallèle avec des pthreads dans php

Quelles sont les conditions préalables à l'utilisation de pthreads en php?

Pour utiliser des PTHreads en PHP, vous devez avoir une connaissance pratique de PHP et de programmation orientée objet. Vous devez également installer ZTS (Zend Thread Safety) activé PHP. Pthreads n'est pas disponible dans une installation PHP standard; Vous pouvez vérifier si votre installation PHP a des ZTS activées en exécutant la commande "PHP -i | grep" Thread Safety "" dans le terminal. S'il renvoie "Thread Safety = & gt; activé", vous pouvez utiliser des pthreads.

Comment installer des pthreads en php?

Pour installer des pthreads, vous devez utiliser PECL, qui est la bibliothèque communautaire d'extension PHP. Tout d'abord, assurez-vous que le PHP compatible ZTS soit installé. Ensuite, dans votre terminal, exécutez la commande "PECL install pthreads". Si l'installation est réussie, vous devez ajouter la ligne "Extension = pthreads.so" à votre fichier php.ini. Cela chargera l'extension pthreads chaque fois que PHP est exécuté.

Comment créer un nouveau thread en PHP à l'aide de PTHEADS?

Pour créer un nouveau thread, vous devez définir une classe qui étend la classe de threads fournie par Pthreads. Dans cette classe, vous remplacerez la méthode run (), qui est le code qui sera exécuté dans un nouveau thread. Vous pouvez ensuite créer une instance de cette classe et appeler sa méthode start () pour démarrer un nouveau fil.

Comment utiliser les Pthreads pour partager les données entre les threads en PHP?

pthreads fournit la classe filetée pour partager les données entre les threads. Vous pouvez créer une nouvelle instance de cette classe et la transmettre à votre fil. Toutes les propriétés définies sur cet objet seront bien partagées entre les threads.

Comment gérer les erreurs dans Pthreads?

La gestion des erreurs dans les PTHreads est similaire à la gestion des erreurs en php standard. Vous pouvez utiliser le bloc d'essai pour capter des exceptions. Cependant, notez que chaque thread a sa propre portée, donc les exceptions dans un thread n'affecteront pas d'autres threads.

Puis-je utiliser des pthreads dans des cadres PHP tels que Laravel ou Symfony?

pthreads est incompatible avec des cadres PHP tels que Laravel ou Symfony. En effet, ces frameworks ne sont pas conçus pour être en filetage. Si vous devez effectuer un traitement parallèle dans ces cadres, envisagez d'utiliser d'autres techniques telles que des files d'attente ou des tâches asynchrones.

Comment déboguer les scripts PHP à l'aide de pthreads?

Le débogage des scripts PHP utilisant des PTHreads peut être difficile car chaque thread s'exécute dans son propre contexte. Cependant, vous pouvez utiliser des techniques de débogage standard telles que l'enregistrement ou la sortie des données sur la console. Vous pouvez également utiliser PHP Debuggers comme XDebug, mais sachez que tous les débogueurs ne prennent pas en charge les applications multithread.

Puis-je utiliser des pthreads dans un environnement de serveur Web?

pthreads n'est pas recommandé dans les environnements de serveurs Web. Il est conçu pour les scripts CLI (interface de ligne de commande). L'utilisation de Pthreads dans un environnement de serveur Web peut conduire à des résultats imprévisibles et est souvent dangereux.

Comment utiliser des pthreads pour arrêter un thread en cours d'exécution en PHP?

Pour arrêter un thread en cours d'exécution, vous pouvez utiliser la méthode Kill () fournie par Pthreads. Cependant, cette méthode doit être utilisée avec prudence, car elle peut conduire à des résultats imprévisibles si le thread est en fonctionnement. Il est généralement préférable de concevoir vos fils afin qu'ils puissent effectuer leurs tâches proprement.

Existe-t-il une alternative aux pthreads pour la programmation parallèle en PHP?

Oui, il existe plusieurs alternatives aux PTHreads pour la programmation parallèle dans PHP. Ceux-ci incluent les fourches, une extension PECL qui fournit des interfaces pour la création et la gestion des processus enfants, et parallèle, une extension PHP native introduite dans PHP 7.2, qui fournit une interface de programmation parallèle plus simple et plus sûre.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal