Concurrence
Dix mille connexions qui dorment, cent appels sortants Ă lancer dâun coup, un calcul qui devrait occuper tous les cĆurs : un jour ou lâautre, vos requĂȘtes cessent dâĂȘtre indĂ©pendantes les unes des autres, et câest lĂ que commence votre troisiĂšme question. PHP nâa ni threads dans sa version standard, ni boucle dâĂ©vĂ©nements intĂ©grĂ©e. La concurrence lui vient des processus, de bibliothĂšques bĂąties sur les coroutines que le langage a ajoutĂ©es en 8.1, et de runtimes qui apportent leur propre boucle dâĂ©vĂ©nements. Chacune de ces trois rĂ©ponses convient Ă certaines charges et Ă©choue sur dâautres, et câest cette diffĂ©rence quâil faut connaĂźtre avant de lui confier un service.
Beaucoup de requĂȘtes
Un worker qui attend 40 millisecondes une rĂ©ponse de la base occupe un processus pendant 40 millisecondes, et aucune autre requĂȘte ne sâen aperçoit. Tout le modĂšle tient lĂ , pour le cas ordinaire des requĂȘtes HTTP indĂ©pendantes : le pool de processus du chapitre Le runtime, oĂč chaque worker bloque sur sa requĂȘte SQL, sa lecture de cache ou son appel HTTP sortant pendant que le systĂšme dâexploitation fait tourner les autres. Un runtime Ă boucle dâĂ©vĂ©nements comme Node.js a besoin de code asynchrone pour quâun appel lent ne gĂšle pas toutes les connexions. PHP a prĂ©fĂ©rĂ© donner un processus Ă chaque connexion, et le code reste synchrone.
Cette simplicitĂ© se paie en mĂ©moire, connexion par connexion. Un worker qui garde un WebSocket ouvert, qui attend la fin dâun long poll ou qui diffuse une rĂ©ponse pendant une minute immobilise un processus entier tout ce temps, et un pool de quelques centaines de processus ne tiendra jamais dix mille connexions inactives. Pour cette charge-lĂ , le modĂšle par dĂ©faut de PHP est le mauvais.
Beaucoup de connexions
Pour les charges qui exigent de nombreuses connexions simultanĂ©es dans un seul processus, PHP dispose de runtimes asynchrones, et ce sont des bibliothĂšques ou des extensions, pas des fonctionnalitĂ©s du langage. Par ordre alphabĂ©tique : AMPHP, un ensemble de bibliothĂšques non bloquantes construites sur une boucle dâĂ©vĂ©nements et sur les fibers du langage ; ReactPHP, un ensemble de bibliothĂšques bas niveau pilotĂ©es par les Ă©vĂ©nements, avec sa propre boucle, son serveur HTTP et ses composants de flux ; Swoole enfin, ou son fork OpenSwoole, une extension en C qui donne Ă PHP une boucle dâĂ©vĂ©nements, des coroutines et un serveur HTTP et WebSocket intĂ©grĂ©, avec des appels dâentrĂ©es-sorties qui rendent la main au lieu de bloquer. FrankenPHP et RoadRunner, les runtimes worker du chapitre Le runtime, ne figurent pas dans cette liste parce quâils rĂ©pondent Ă un autre besoin : ils gardent lâapplication chargĂ©e entre deux requĂȘtes, toujours une requĂȘte Ă la fois par worker, et ne rendent pas le code concurrent.
Sous les deux premiers se trouve la Fiber, arrivĂ©e en PHP 8.1 : une fonction capable de se suspendre elle-mĂȘme, et que celui qui la tient peut reprendre plus tard. La RFC qui lâa introduite dit trĂšs prĂ©cisĂ©ment ce quâelle nâest pas : « toutes les fibers vivent dans un seul thread, une seule fiber sâexĂ©cute Ă la fois », et « le code bloquant, comme file_get_contents(), continuera de bloquer le processus entier, mĂȘme si dâautres fibers existent ». Le langage ne fournit aucun ordonnanceur. Câest une bibliothĂšque qui lâapporte, et chaque appel dâentrĂ©e-sortie du programme doit passer par elle pour rendre la main. Une application PHP asynchrone sâĂ©crit donc contre le client HTTP, le client de base de donnĂ©es et les fonctions de fichiers dâAMPHP ou de ReactPHP, et non contre les fonctions standard. Une seule bibliothĂšque synchrone glissĂ©e par mĂ©garde dans les dĂ©pendances bloque toute la boucle.
JusquâoĂč une boucle dâĂ©vĂ©nements porte PHP, le test plaintext du benchmark multi-langages le montre : il ne fait aucun travail applicatif et mesure seulement combien de connexions en pipeline une couche HTTP sait servir.
LâentrĂ©e Swoole sert 3,5 millions de requĂȘtes par seconde, devant Micronaut, Gin, Node.js et Spring, quand PHP nu sous PHP-FPM en sert 450âŻ000 et Laravel sous PHP-FPM 27âŻ000. Le plafond est ailleurs : ASP.NET Core Ă 11,5 millions, et tout en haut un serveur Python adossĂ© Ă du C et un serveur Rust, autour de 28 millions. Avec une boucle dâĂ©vĂ©nements, PHP joue dans le deuxiĂšme groupe, pas dans le premier. Sans, il joue dans le quatriĂšme.
Beaucoup de cĆurs
Le programme spectral-norm le plus rapide en PHP dans le Benchmarks Game termine en 18 secondes de temps rĂ©el pour 72 secondes de CPU rĂ©parties sur plusieurs cĆurs, contre 90 secondes pour le meilleur programme Python et 1,6 seconde pour Node.js. Cet Ă©cart entre temps CPU et temps rĂ©el, câest Ă cela que ressemble le calcul parallĂšle en PHP, et une fois de plus il vient des processus. LâinterprĂ©teur en ligne de commande forke avec pcntl_fork(), lance des sous-processus avec proc_open() ou fait tourner plusieurs scripts sous un superviseur, et les rĂ©sultats reviennent par des tubes, des fichiers, une base ou une file dâattente. Lâextension parallel propose bien des threads, mais sur la version thread-safe de lâinterprĂ©teur, que peu de distributions livrent. Le parallĂ©lisme par fork fonctionne, et il fonctionne dans la classe de vitesse de lâinterprĂ©teur dĂ©crite dans DĂ©bit et latence.
Lâordonnanceur entrera-t-il un jour dans le moteur ? La question est ouverte sur la liste internals. Une RFC intitulĂ©e « Concurrency Support in the PHP Engine », qui ajouterait un ordonnanceur et de la concurrence structurĂ©e au langage lui-mĂȘme, y Ă©tait en discussion au moment oĂč jâĂ©cris, sans vote, aprĂšs quâune premiĂšre proposition sur le mĂȘme sujet a Ă©tĂ© abandonnĂ©e en juillet 2026. En septembre 2026, le langage a des fibers, et lâordonnanceur est une bibliothĂšque.
Ce qui ne doit pas faire attendre lâutilisateur
La concurrence dont vous aurez besoin le plus souvent nâa rien de spectaculaire : câest le travail que lâutilisateur ne devrait pas avoir Ă attendre. Une requĂȘte a une durĂ©e limite et une rĂ©ponse Ă rendre, donc tout ce qui prend plus de temps doit en sortir. Lâidiome, câest une file dâattente et un processus worker. La requĂȘte dĂ©pose un travail dans une table ou un courtier de messages, puis rĂ©pond. De son cĂŽtĂ©, un script PHP en ligne de commande, maintenu en vie par un superviseur, dĂ©pile la file et exĂ©cute les travaux un par un. Ce script nâa ni limite de temps ni limite de mĂ©moire tant que vous ne lui en donnez pas, alors un worker de production fixe les deux et sâarrĂȘte proprement aprĂšs quelques milliers de travaux, pour que le superviseur relance un processus neuf. La plupart des frameworks full-stack livrent ce schĂ©ma avec un pilote pour les courtiers habituels, et cron couvre les tĂąches planifiĂ©es.
La limite : la concurrence de PHP est Ă gros grain. Des processus pour les requĂȘtes, des processus pour les cĆurs, une file dâattente pour le travail en arriĂšre-plan, et un runtime Ă part, avec ses propres bibliothĂšques dâentrĂ©es-sorties, pour le cas des connexions nombreuses. Si votre produit est un service temps rĂ©el avec des dizaines de milliers de connexions ouvertes, vous passerez votre premier mois Ă choisir puis Ă apprendre lâun des runtimes asynchrones, et vous dĂ©couvrirez que la plupart des bibliothĂšques de lâĂ©cosystĂšme nâont pas Ă©tĂ© Ă©crites pour lui. Si votre produit est fait de requĂȘtes et de rĂ©ponses, vous ne rencontrerez jamais la question.
Ce que vous pouvez vĂ©rifier vous-mĂȘme
Prenez le scĂ©nario Ă connexions nombreuses que vous avez vraiment, si vous en avez un, et Ă©crivez-le deux fois dans lâaprĂšs-midi : une fois avec AMPHP ou ReactPHP, une fois avec Swoole ou OpenSwoole. Comptez ensuite combien des bibliothĂšques dont vous dĂ©pendriez offrent un client non bloquant. Si câest la plupart, la voie asynchrone vous est ouverte. Si elles sont rares, la conclusion honnĂȘte est que PHP est le mauvais outil pour ce service-lĂ , et le reste de votre systĂšme est une dĂ©cision Ă part.
Reste Ă savoir si le langage lui-mĂȘme est de ceux que vous auriez envie dâĂ©crire, et cette question-lĂ , vous y rĂ©pondez en le lisant.