Le contenu de cet article explique comment PHP utilise la ligne de commande pour implémenter le traitement des tâches (code) en mode multi-processus asynchrone. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. à vous.
Utiliser PHP pour implémenter des tâches asynchrones a toujours été un problème parmi les solutions existantes : les frameworks asynchrones bien connus de PHP incluent swoole et Workerman, mais ils ne peuvent pas être utilisés directement dans l'environnement Web, même si forcé Pour créer un environnement Web, les appels asynchrones sont également implémentés en mode multi-processus. Mais parfois, il n'est vraiment pas nécessaire de démarrer le service et de laisser le serveur attendre les messages des clients, sans compter que le code du serveur ne peut pas être modifié au milieu. Cet article expliquera comment implémenter des appels multi-processus et asynchrones dans l'environnement Web dans l'environnement CLI sans utiliser de framework ou de bibliothèque tierce.
Appels asynchrones dans l'environnement Web
Il existe deux méthodes couramment utilisées
1 Utiliser la connexion socket.
Cette méthode est une architecture C/S typique et nécessite la prise en charge du serveur.
// 1. 创建socket套接字 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // 2. 进行socket连接 socket_connect($socket, '127.0.0.1', '3939'); //socket_set_nonblock($socket); // 以非阻塞模式运行,由于在客户端不实用,所以这里不考虑 // 3. 向服务端发送请求 socket_write($socket, $request, strlen($request)); // 4. 接受服务端的回应消息(忽略非阻塞的情况,如果服务端不是提供异步服务,那这一步可以省略) $recv = socket_read($socket, 2048); // 5. 关闭socket连接 socket_close($socket);
2. Utilisez popen pour ouvrir le pipeline de processus
Cette méthode utilise les commandes du système d'exploitation et est directement exécutée par le système d'exploitation.
Les appels asynchrones abordés dans cet article utilisent cette méthode.
$sf = '/path/to/cli_async_task.php'; //要执行的脚本文件 $op = 'call'; //脚本文件接收的参数1 $data = base64_encode(serialize(['TestTask', 'arg1', 'arg2'])); //脚本文件接收的参数2 pclose(popen("php '$sf' --op $op --data $data &", 'r')); //打开之后接着就关闭进程管道,让该进程以守护模式运行 echo PHP_EOL.'异步任务已执行。'.PHP_EOL;
L'avantage de cette méthode est qu'elle peut être résolue en une seule étape et que le processus actuel ne nécessite aucune surcharge.
L'inconvénient est également évident : l'état d'exécution du script de tâche ne peut pas être suivi.
Le point culminant sera donc le fichier de script qui exécute la tâche. Ce qui suit présentera la mise en œuvre du traitement des tâches et du multi-traitement.
CLI
environnementRemarque : le mode multi-processus ne prend en charge que Linux, pas Windows! !
Chaque étape sera introduite ici à partir de 0 (sans utiliser de framework ou de bibliothèque de classes), Un code complet sera joint à la fin.
Un aspect qui ne peut être ignoré dans aucun script est la gestion des erreurs. Ainsi, lors de l’écriture d’un script de traitement de tâches, la première étape consiste à écrire une méthode de gestion des erreurs.
En PHP, il vous suffit d'appeler les trois fonctions set_exception_handler set_error_handler register_shutdown_function, puis d'écrire une méthode de traitement personnalisée.
L'étape suivante consiste à définir la fonction de chargement automatique spl_autoload_register pour éviter les problèmes de require/include à chaque fois qu'une nouvelle classe est utilisée.
Définir la méthode d'opération de journalisation.
Définir la méthode de traitement des tâches.
Lisez les paramètres depuis la ligne de commande et commencez à exécuter la tâche.
PHP crée plusieurs processus en utilisant la fonction pcntl_fork, qui créera une copie du processus actuel (technique de clone fantôme), donc il y a Il y a deux processus, le processus actuel est le processus principal (l'ontologie) et le processus forké est le processus enfant (le clone fantôme). Il convient de noter que l'environnement de code des deux processus est le même et que les deux processus se sont exécutés à l'emplacement de la fonction pcntl_fork. La différence est que le numéro de processus obtenu par getmypid est différent. La différence la plus importante est que lorsque la fonction pcntl_fork est appelée, la valeur de retour obtenue par le processus enfant est 0, tandis que le processus principal obtient le numéro de processus pid du processus enfant. .
D'accord, une fois que nous savons qui est le processus enfant, nous pouvons laisser le processus enfant effectuer des tâches.
Alors, comment le processus principal connaît-il l'état du processus enfant ?
Utilisez pcntl_wait. Cette fonction a deux paramètres $status et $options. $status est un type de référence, utilisé pour stocker l'état du processus enfant. $options a deux constantes facultatives WNOHANG|WUNTRACED, ce qui signifie revenir immédiatement sans attendre la fin du processus enfant. et en attente du processus enfant respectivement. Le processus se termine. Évidemment, l'utilisation de WUNTRACED bloquera le processus principal. (Vous pouvez également utiliser la fonction pcntl_waitpid pour obtenir le statut spécifique du processus enfant pid)
Dans plusieurs processus, ce que le processus principal doit faire est de gérer le statut de chaque processus enfant, sinon le processus enfant est susceptible être incapable de sortir et devenir un processus zombie.
À propos de la communication de messages entre plusieurs processus
Ce domaine doit impliquer une logique métier spécifique, je ne peux donc que le mentionner brièvement. Sans envisager l'utilisation de services tiers tels que redis
, PHP peut implémenter nativement des méthodes telles que la communication par pipeline et la mémoire partagée. La mise en œuvre est relativement simple, mais l'inconvénient est que la capacité de données utilisable est limitée et que les données ne peuvent être échangées qu'à l'aide de simples protocoles textuels.
Comment terminer manuellement toutes les tâches de processus
如果多进程处理不当,很可能导致进程任务卡死,甚至占用过多系统资源,此时只能手动结束进程。
除了一个个的根据进程号来结束,还有一个快速的方法是首先在任务脚本里自定义进程名称,就是调用cli_set_process_title函数,然后在命令行输入:ps aux|grep cli_async_worker |grep -v grep|awk '{print $2}'|xargs kill -9 (里面的 cli_async_worker 就是自定义的进程名称),这样就可以快速结束多进程任务了。
以下是完整的任务执行脚本代码:
可能无法直接使用,需要修改的地方有:
脚本目录和日志目录常量
自动加载任务类的方法(默认是加载脚本目录中以Task
结尾的文件)
其他的如:错误和日志处理方式和文本格式就随意吧...
如果命名管道文件设置有错误,可能导致进程假死,你可能需要手动删除进程管道通信的代码。
多进程的例子:execAsyncTask('multi', [ 'test' => ['a', 'b', 'c'], 'grab' => [['url' => 'https://www.baidu.com', 'callback' => 'http://localhost']] ]);。执行情况可以在日志文件中查看。execAsyncTask函数参考【__使用popen打开进程管道__】。
<?php error_reporting(E_ALL ^ E_NOTICE ^ E_USER_WARNING); @ini_set('display_errors', 0); @ini_set('date.timezone', 'PRC'); chdir(__DIR__); /* 任务脚本目录 */ defined('TASK_PATH') or define('TASK_PATH', realpath(__DIR__ .'/tasks')); /* 任务日志目录 */ defined('TASK_LOGS_PATH') or define('TASK_LOGS_PATH', __DIR__ .'/tasks/logs'); if (!is_dir(TASK_LOGS_PATH)) @mkdir(TASK_LOGS_PATH, 0777, true); set_exception_handler(function($e) { $time = date('H:i:s', time()); $msg = sprintf(''. '<h3>[%s] %s (%s)</h3>'. "\n". '<pre class="brush:php;toolbar:false">%s', $time, $e->getMessage(), $e->getCode(), $e->getTraceAsString() ); file_put_contents(TASK_LOGS_PATH .'/exception-'.date('Ymd').'.log', $msg.PHP_EOL, FILE_APPEND|LOCK_EX); }); set_error_handler(function($errno, $errmsg, $filename, $line) { if (!(error_reporting() & $errno)) return; ob_start(); debug_print_backtrace(); $backtrace = ob_get_contents(); ob_end_clean(); $datetime = date('Y-m-d H:i:s', time()); $msg = <<
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!