Multiplexage de Fibers
OxPHP utilise les Fibers de PHP pour traiter plusieurs requêtes HTTP simultanément sur un seul thread worker. Lorsqu'une requête appelle oxphp_sleep() ou oxphp_async_await() (lorsque le pool asynchrone est activé), elle se suspend et le thread worker prend immédiatement en charge la requête suivante. Un seul worker peut gérer des centaines de requêtes en cours sans threads supplémentaires.
Fonctionnement
L'ordonnanceur exécute de nombreuses requêtes sur un seul thread, en attribuant à chacune sa propre Fiber et en basculant de l'une à l'autre chaque fois qu'une Fiber se suspend.
- Une requête arrive et l'ordonnanceur l'assigne à une Fiber : un contexte d'exécution léger doté de sa propre pile et de son propre état PHP.
- La Fiber exécute le gestionnaire
oxphp_worker(). Si le gestionnaire se termine sans se suspendre, la réponse est envoyée et la Fiber est recyclée sans surcoût par rapport à un worker à requête unique. - Si le gestionnaire appelle une fonction suspensive (
oxphp_sleep(),oxphp_usleep(),oxphp_async_await()), la Fiber rend la main à l'ordonnanceur. - L'ordonnanceur prend en charge les nouvelles requêtes entrantes (en créant de nouvelles Fibers) et reprend les Fibers suspendues dont les conditions d'attente sont satisfaites (minuterie expirée, résultat asynchrone prêt).
- L'état PHP de chaque Fiber (superglobales, en-têtes de réponse, tampons de sortie, pile de la VM) est sauvegardé lors de la suspension et restauré à la reprise. Les Fibers sont totalement isolées les unes des autres.
graph LR W["Worker Thread"] W --> A0["Fiber A: handling /api/users"] A0 --> A1["oxphp_sleep(0.5)"] A1 --> A2["suspended"] A2 --> A3["resumed"] A3 --> A4["response"] W --> B0["Fiber B: handling /api/orders"] B0 --> B1["oxphp_async_await($p)"] B1 --> B2["suspended"] B2 --> B3["resumed"] B3 --> B4["response"] W --> C0["Fiber C: handling /health"] C0 --> C1["response (no suspension, zero overhead)"]
Configuration
Le multiplexage de Fibers s'active automatiquement lorsque le mode worker est activé. Aucune variable d'environnement supplémentaire n'est nécessaire.
| Variable | Par défaut | Description |
|---|---|---|
WORKER_MODE_ENABLED |
false |
Définissez sur true et faites pointer ENTRY_FILE vers un bootstrap .php pour activer le mode worker et le multiplexage de Fibers |
PHP_WORKERS |
CPU / 2 (min 1) | Nombre de threads worker. Chaque thread exécute son propre ordonnanceur indépendant, avec jusqu'à 256 Fibers concurrentes |
Le nombre maximal de Fibers concurrentes par thread worker est de 256. Avec 4 threads worker, OxPHP peut traiter jusqu'à 1 024 requêtes en cours simultanément.
Points de suspension
Ces fonctions suspendent la Fiber courante et permettent à d'autres requêtes de s'exécuter sur le même thread :
| Fonction | Ce qui se passe |
|---|---|
oxphp_sleep(float $seconds) |
Suspend la Fiber pendant la durée indiquée. Les autres Fibers continuent de s'exécuter |
oxphp_usleep(int $microseconds) |
Identique à oxphp_sleep() avec une granularité à la microseconde (minimum 1 ms) |
oxphp_async_await(int $promise_id) |
Suspend la Fiber jusqu'à ce que la tâche asynchrone se termine sur le pool de threads en arrière-plan |
Ces fonctions ne suspendent pas la Fiber :
| Fonction | Comportement |
|---|---|
oxphp_stream_flush() |
Envoie immédiatement un fragment au client et retourne. À utiliser avec oxphp_sleep() dans les boucles SSE |
oxphp_finish_request() |
Envoie la réponse complète et poursuit l'exécution PHP. Ne rend pas la main |
Les fonctions natives sleep() et usleep() de PHP bloquent l'intégralité du thread worker. Utilisez toujours oxphp_sleep() et oxphp_usleep() pour obtenir un comportement coopératif.
Exemples PHP
Traitement concurrent de base
Les requêtes qui ne se suspendent pas s'exécutent à pleine vitesse, sans surcoût lié aux Fibers :
<?php
oxphp_worker(function () {
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($path === '/health') {
echo json_encode(['status' => 'ok']);
return; // No suspension — runs at full speed
}
if ($path === '/slow') {
oxphp_sleep(2.0); // Yields for 2 seconds — other requests run
echo "Done after 2s delay";
return;
}
echo "Hello";
});Appels d'API non bloquants
Combinez oxphp_async() et oxphp_async_await() pour effectuer des appels d'API externes sans bloquer le worker :
<?php
oxphp_worker(function () {
// Dispatch two API calls to the async thread pool
$p1 = oxphp_async(fn() => file_get_contents('https://api.example.com/users'));
$p2 = oxphp_async(fn() => file_get_contents('https://api.example.com/orders'));
// Await both — the fiber suspends, other requests run on this thread
$users = oxphp_async_await($p1);
$orders = oxphp_async_await($p2);
header('Content-Type: application/json');
echo json_encode(['users' => json_decode($users), 'orders' => json_decode($orders)]);
});SSE avec sommeil coopératif
Entrelacez les Server-Sent Events avec d'autres requêtes à l'aide de oxphp_stream_flush() et oxphp_sleep() :
<?php
oxphp_worker(function () {
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
for ($i = 0; $i < 30; $i++) {
echo "data: " . json_encode(['count' => $i, 'time' => time()]) . "\n\n";
oxphp_stream_flush(); // Send chunk now (does not suspend)
oxphp_sleep(1.0); // Yield for 1 second (other requests run)
}
});E/S bloquantes
Le multiplexage de Fibers est coopératif, pas préemptif. Une Fiber qui appelle une fonction bloquante gèle l'intégralité du thread worker : aucune autre Fiber de ce thread ne peut progresser.
Fonctions qui bloquent le worker
file_get_contents(),fopen(),fread()curl_exec(),curl_multi_exec()- Les requêtes PDO,
mysqli_query() sleep(),usleep()de PHP (utilisez plutôtoxphp_sleep())- La résolution DNS (
gethostbyname()) - Toute E/S réseau ou disque synchrone
Comment éviter le blocage
Enveloppez les opérations bloquantes dans oxphp_async() pour les exécuter sur le pool de threads asynchrone :
<?php
// WRONG — blocks the entire worker thread
$html = file_get_contents('https://example.com');
// CORRECT — runs on async pool, fiber yields
$promise = oxphp_async(fn() => file_get_contents('https://example.com'));
$html = oxphp_async_await($promise);Pour les requêtes de base de données :
<?php
$db = new PDO('mysql:host=db;dbname=app', 'root', 'secret');
// WRONG — blocks the worker
$users = $db->query('SELECT * FROM users WHERE active = 1')->fetchAll();
// CORRECT — query runs on async thread, fiber yields
$promise = oxphp_async(function () {
$db = new PDO('mysql:host=db;dbname=app', 'root', 'secret');
return $db->query('SELECT * FROM users WHERE active = 1')->fetchAll();
});
$users = oxphp_async_await($promise);Les connexions de base de données ne peuvent pas être passées à oxphp_async(), car les objets ne sont pas sérialisables entre les threads. Créez la connexion à l'intérieur de la closure asynchrone, ou effectuez la requête directement dans la Fiber si celle-ci est suffisamment rapide pour que le blocage soit acceptable.
oxphp_async() nécessite ASYNC_WORKERS > 0. Lorsque le pool asynchrone est désactivé (par défaut), l'appel à oxphp_async() lève OxPHP\Async\AsyncException.
Comment les Fibers sont recyclées
Les piles C des Fibers sont allouées une seule fois et réutilisées d'une requête à l'autre. Lorsqu'une Fiber termine le traitement d'une requête, elle n'est pas détruite ; elle se suspend plutôt et rend la main à l'ordonnanceur, puis elle est ajoutée à une liste libre. La requête suivante réutilise la pile C existante, ce qui évite une allocation mémoire coûteuse.
Les piles de la VM PHP (utilisées pour les cadres d'appel de fonction) sont allouées à neuf pour chaque requête et libérées lorsque le gestionnaire retourne.
Exemple Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "80:80"
environment:
- DOCUMENT_ROOT=/var/www/html/public
- WORKER_MODE_ENABLED=true
- ENTRY_FILE=worker.php
- PHP_WORKERS=4
- ASYNC_WORKERS=8Avec cette configuration, chacun des 4 threads worker peut gérer jusqu'à 256 Fibers concurrentes, et les E/S bloquantes sont déchargées vers 8 threads worker asynchrones.
Dépannage
Les requêtes deviennent lentes lorsqu'une requête effectue de lourdes E/S
Une Fiber appelle une fonction bloquante (requête de base de données, requête HTTP, lecture de fichier) sans oxphp_async(). Cela bloque l'intégralité du thread worker.
Correctif : Enveloppez les appels bloquants dans oxphp_async() :
<?php
$promise = oxphp_async(fn() => file_get_contents($url));
$result = oxphp_async_await($promise);"Async pool is disabled. Set ASYNC_WORKERS > 0 to enable."
Le pool asynchrone n'est pas configuré. Lorsque ASYNC_WORKERS=0 (par défaut), toutes les fonctions asynchrones lèvent OxPHP\Async\AsyncException.
Correctif : Définissez ASYNC_WORKERS sur une valeur positive :
ASYNC_WORKERS=8"Failed to dispatch async task" lors de l'utilisation de oxphp_async()
Le pool asynchrone fonctionne, mais il a atteint sa capacité maximale.
Correctif : Augmentez ASYNC_WORKERS ou ASYNC_QUEUE_CAPACITY :
ASYNC_WORKERS=8
ASYNC_QUEUE_CAPACITY=512oxphp_sleep() ne rend pas la main aux autres requêtes
Le multiplexage de Fibers ne fonctionne qu'en mode worker. En mode traditionnel, oxphp_sleep() se rabat sur un usleep() bloquant.
Correctif : Activez le mode worker en définissant WORKER_MODE_ENABLED=true.
Consommation mémoire élevée avec de nombreuses requêtes concurrentes
Chaque Fiber utilise une pile C (8 MiB par défaut, configurée via le paramètre ini fiber.stack_size de PHP) ainsi qu'une pile de la VM PHP par requête. Avec 256 Fibers concurrentes, la consommation de pile C dans le pire des cas atteint 2 GiB par thread worker.
Correctif : Réduisez fiber.stack_size dans php.ini si votre application n'utilise pas de récursion profonde :
fiber.stack_size = 512KLimitations
- Mode worker uniquement — le multiplexage de Fibers n'est pas disponible en mode traditionnel
- 256 Fibers par worker — limite stricte, non configurable à l'exécution
- Coopératif uniquement — le code gourmand en CPU (boucles serrées, calculs lourds) prive les autres Fibers de ressources. Il n'y a pas de préemption
- Les E/S bloquantes bloquent le thread — tous les appels bloquants doivent être enveloppés dans
oxphp_async()pour une véritable concurrence - Les fonctions natives
sleep()/usleep()de PHP ne prennent pas en charge les Fibers — utilisezoxphp_sleep()/oxphp_usleep() oxphp_async_await_race()etoxphp_async_await_any()ne rendent pas la main — elles bloquent actuellement même à l'intérieur d'une Fiber.oxphp_async_await_all()suspend bel et bien la Fiber pendant l'attente, elle est donc compatible avec les Fibers ; pourrace/any, utilisez des appels séquentiels àoxphp_async_await()si vous avez besoin que le thread reste coopératif
Voir aussi
- Mode worker — processus PHP persistants et l'API
oxphp_worker() - Promesses asynchrones — pool de threads en arrière-plan pour décharger les E/S bloquantes
- SSE — streaming en temps réel combiné à un sommeil coopératif basé sur les Fibers
- Fonctions PHP —
oxphp_sleep(),oxphp_usleep()et d'autres fonctions compatibles avec les Fibers - Référence de configuration —
WORKER_MODE_ENABLED,ENTRY_FILE,PHP_WORKERS,ASYNC_WORKERS