Maison > Article > développement back-end > php : concurrence avec les processus. point. communication interprocessus avec shmop
php n'est pas le genre de langage dans lequel les développeurs pensent généralement à des choses comme la mémoire. nous nous contentons en quelque sorte de parcourir les variables et les fonctions et de laisser les internes comprendre tous ces «trucs de bélier» pour nous. changeons ça.
dans la première partie de cette série, nous avons construit un script php capable d'exécuter un certain nombre de tâches simultanément en bifurquant des processus enfants. cela fonctionnait plutôt bien, mais il y avait un problème flagrant non résolu : il n'y avait aucun moyen pour ces processus enfants de renvoyer des données au processus parent.
dans cet article, nous allons résoudre ce problème en utilisant shmop, les "opérations de mémoire partagée" de php.
Lorsqu'un nouveau processus démarre, le système d'exploitation lui attribue une partie de la mémoire à utiliser. les processus ne peuvent pas lire ou écrire dans une mémoire qui n'est pas la leur, car ce serait un cauchemar en matière de sécurité. parfaitement raisonnable.
cela crée cependant un problème pour nous lorsque nous traitons des processus que nous avons créés avec pcntl_fork dans la première partie de cette série, car cela signifie qu'il n'y a pas de moyen facile pour les processus enfants de communiquer entre eux ou avec leur parent. les processus enfants obtiendront une copie de la mémoire de leur parent lors de leur création afin que toutes les variables assignées avant le fork soient accessibles, mais toute modification apportée à ces variables sera limitée au processus enfant. mémoire différente et tout ça. si nous voulons que l'enfant puisse écrire dans une variable que le processus parent peut lire, nous avons un problème.
il existe un certain nombre de solutions pour cela, toutes regroupées sous la catégorie générale des « communications inter-processus » ou ipc. celui que nous allons utiliser pour notre script php est la « mémoire partagée ».
comme son nom l'indique, la mémoire partagée est un bloc de mémoire auquel un nombre arbitraire de processus peuvent accéder. les blocs de mémoire partagée sont identifiés par une clé (espérons-le) unique. tout processus connaissant la clé peut accéder à ce bloc de mémoire. cela permet à un processus enfant de faire rapport à son processus parent ; l'enfant écrira des données dans un bloc de mémoire partagée et, après sa fermeture, le parent lira les données partagées. c'est une solution à la limite de l'élégance.
Bien sûr, il y a quelques armes à feu que nous devrons éviter en faisant cela : nous devrons nous assurer que la clé qui identifie un bloc de mémoire partagée est unique, et nous devrons faire en sorte que la communication en mémoire partagée ne se fasse que d'une seule manière. moyen d'éviter que plusieurs processus tentent tous d'écrire dans le même bloc en même temps et provoquent un désordre. nous couvrirons tout cela dans la mise en œuvre.
php dispose d'une API riche et robuste pour gérer la mémoire partagée. le manuel indique "Shmop est un ensemble de fonctions facile à utiliser qui permet à PHP de lire, écrire, créer et supprimer des segments de mémoire partagée Unix", et... ce n'est pas faux.
examinons les principales étapes d'utilisation de la mémoire partagée :
créer une clé unique : toute la mémoire partagée est identifiée par une clé. tout processus connaissant la clé d'un bloc de mémoire partagée peut y accéder. Traditionnellement, cette clé est créée en générant des données à partir du système de fichiers (c'est-à-dire une valeur construite à partir de l'inode d'un fichier existant) car le système de fichiers est quelque chose que tous les processus ont en commun. nous utiliserons ftok pour cela.
attribuer un bloc mémoire à l'aide de la clé : un processus peut utiliser la clé d'un bloc mémoire partagé pour l'ouvrir à l'aide de shmop_open. si le bloc de mémoire partagée n'existe pas, cela le crée. la valeur de retour de la fonction open est un pointeur qui peut être utilisé pour la lecture et l'écriture. si vous avez déjà utilisé fopen et fwrite auparavant, ce processus devrait vous être familier.
écrire des données dans le bloc mémoire : l'écriture dans la mémoire partagée a une interface très similaire à celle de fwrite. le pointeur est utilisé et la chaîne à écrire en mémoire est passée en argument. la fonction pour ce faire s'appelle shmop_write.
lire les données du bloc mémoire : la lecture des données de la mémoire partagée se fait avec shmop_read, toujours en utilisant le pointeur de shmop_open. la valeur de retour est une chaîne.
supprimer le bloc mémoire à l'aide de la clé : supprimer la mémoire partagée une fois qu'elle n'est plus nécessaire est importante. cela se fait avec shmop_delete.
commençons par un exemple. le code ci-dessous fonctionne et, si vous n'êtes pas suffisamment curieux ou si vous êtes du type tl;dr, vous pouvez simplement copier-coller-modifier cela, mais pour tout le monde, nous passerons en revue toutes les étapes de shmop et expliquerons ce qu'elles font et comment ils fonctionnent.
// the file used by ftok. can be any file. $shmop_file = "/usr/bin/php8.3"; for($i = 0; $i < 4; $i++) { // create the fork $pid = pcntl_fork(); // an error has ocurred if($pid === -1) { echo "error".PHP_EOL; } // child process else if(!$pid) { // create a random 'word' for this child to write to shared memory $random_word = join(array_map(fn($n) => range('a', 'z')[rand(0, 25)], range(1,5))); // write to shmop $shm_key = ftok($shmop_file, $i); $shm_id = shmop_open($shm_key, 'n', 0755, 1024); shmop_write($shm_id, $random_word, 0); print "child $i wrote '$random_word' to shmop".PHP_EOL; // terminate the child process exit(0); } } // wait for all child processes to finish while(($pid = pcntl_waitpid(0, $status)) != -1) { echo "pid $pid finished".PHP_EOL; } // read all our shared memories for($i = 0; $i < 4; $i++) { // recreate the shm key $shm_key = ftok($shmop_file, $i); // read from the shared memory $shm_id = shmop_open($shm_key, 'a', 0755, 1024); $shmop_contents = shmop_read($shm_id, 0, 1024); print "reading '$shmop_contents' from child $i".PHP_EOL; // delete the shared memory so the shm key can be reused in future runs of this script shmop_delete($shm_id); }
Comme nous l'avons vu ci-dessus, tous les blocs de mémoire partagée sont identifiés par une clé entière unique et avant de pouvoir nous lancer dans la tâche d'attribution de mémoire, nous devons créer cette clé.
en toute honnêteté, nous pouvons utiliser n'importe quel entier que nous voulons, à condition qu'il soit unique, mais la manière canonique généralement acceptée de le faire est d'utiliser ftok pour créer un entier en utilisant un fichier existant dans le système de fichiers comme fichier. point de référence.
la justification de cette démarche est assez simple. les processus ne savent rien les uns des autres, ce qui rend difficile le partage d'une valeur mutuellement convenue. Cependant, l'une des rares choses que tous les processus d'un système ont en commun est le système de fichiers. par conséquent, ftok.
en plus du chemin d'accès à un fichier existant, ftok prend également un argument project_id. il s'agit, selon la documentation, d'une « chaîne d'un caractère », ce que les gens dans tous les autres langages de programmation appelleraient un « caractère ». le but de l'identifiant du projet est d'éviter les collisions lors de la création de la mémoire partagée. si deux projets de deux fournisseurs distincts décidaient tous deux d'utiliser /etc/passwd comme argument pour ftok, le chaos s'ensuivrait.
Regardons un exemple assez simple :
$shm_key = ftok('/usr/bin/php8.3', 'j'); print "shm_key = $shm_key";
ici, nous transmettons le chemin complet vers un fichier dont nous savons qu'il existe sur le système et fournissons un project_id à un caractère, 'j'. si nous exécutons ceci, l'instruction print affichera quelque chose comme :
shm_key = 855706266
c'est un bon entier à utiliser pour créer notre mémoire partagée !
si vous exécutez ce code sur votre système, vous obtiendrez presque certainement une valeur de retour différente, même si vous avez utilisé les mêmes arguments. c'est parce que, sous le capot, ftok utilise l'inode du fichier, et cela est différent d'un système à l'autre.
si, pour une raison quelconque, nous transmettons à ftok un fichier qui n'existe pas, nous recevons un avertissement.
PHP Warning: ftok(): ftok() failed - No such file or directory in <our script> on line <the line> shm_key = -1
notez qu'il ne s'agit que d'un avertissement et que ftok chargera en avant et nous donnera une valeur de -1, ce qui entraînera des problèmes plus tard. soyez prudent.
maintenant, reprenons notre appel à ftok sur la ligne 20 :
$shm_key = ftok($shmop_file, $i);
ici nous avons transmis à ftok le chemin du fichier que nous avons défini dans $shm_key, dans ce cas /usr/bin/php8.3, un fichier dont nous savons qu'il existe sur le système.
pour notre project_id, nous utilisons $i, l'index du tableau sur lequel nous effectuons une boucle. nous faisons cela pour que chacun de nos processus enfants dispose de son propre bloc de mémoire partagée pour stocker ses résultats. rappelez-vous que si plusieurs processus tentent d'écrire dans la mémoire partagée, de mauvaises choses se produisent. utiliser l'index ici nous aide à éviter cela.
Si vous avez déjà accédé à des fichiers avec des outils tels que fopen et fwrite de PHP, alors utiliser shmop vous sera très familier.
commençons par ouvrir un bloc de mémoire partagée avec shmop_open :
$shm_id = shmop_open($shm_key, 'n', 0755, 1024);
cette fonction prend quatre arguments :
une remarque importante ici est que l'appel de shmop_open créera un nouveau bloc de mémoire s'il n'en existe pas déjà un sur cette clé. c'est similaire au comportement de fopen, mais avec shmop_open, ce comportement dépend de l'argument 'mode' que nous passons.
comme le montre l'exemple, shmop_open renvoie un pointeur qui peut être utilisé pour l'accès : lecture ou écriture, selon le mode utilisé pour ouvrir le bloc mémoire.
the mode argument that we pass to shmop_open determines how we can access our shared memory block. there are four options, all covered in the official documentation, but for the sake of simplicity, we'll only look at the two we need for our purposes.
if we look at the example, we can see that when we open the shared memory block in the child process to write our data, we use the n mode. this creates the new memory block in a safe way and returns a pointer that we can write to.
once our child process has created a new shared memory block and received a pointer to it, it can write whatever it wants there using shmop_write.
in our example, doing this looks like:
shmop_write($shm_id, $random_word, 0);
the shmop_write function takes three arguments:
shmop_write returns, as an integer, the number of bytes written.
if you've done file access using fopen, you're probably (hopefully!) in the habit of calling fclose when you're done writing.
we do not do that with shmop.
there is a shmop_close function, but it has been deprecated since php 8.0 and does nothing (other than throw a deprecation warning, that is). the standard practice with shmop is to just leave the pointer 'open' after we're done writing. we'll delete it later.
once all the child processes have written their data to their respective shared memory blocks an exited, all that remains is for the parent process to read that data. the strategy for this is:
let's look again at the example we have for reading shared memory.
// read all our shared memories for($i = 0; $i < 4; $i++) { // recreate the shm key $shm_key = ftok($shmop_file, $i); // read from the shared memory $shm_id = shmop_open($shm_key, 'a', 0755, 1024); $shmop_contents = shmop_read($shm_id, 0, 1024); print "reading '$shmop_contents' from child $i".PHP_EOL; // delete the shared memory so the shm key can be reused in future runs of this script shmop_delete($shm_id); }
when we made the key to create our shared memory blocks, we used ftok with two arguments: the path an existing file in the filesystem, and a 'project id'. for the project id, we used the index of the array we looped over to fork multiple children.
we can use the exact same strategy to recreate the keys for reading. as long as we input the same two arguments into ftok, we get the same value back.
we open the shared memory block for reading almost exactly the same way as we did above for writing. the only difference is the mode.
for reading, we use the a mode. this stands for 'access', and gives us a read-only pointer to our shared memory block.
once we have a pointer to our shared memory block, we can read from it using shmop_read.
shmop_read takes three arguments:
the return type is a string. if there are errors reading, we get a boolean false.
once we are done reading our shared memory, we can delete it.
this is an important step. unlike variables in our script, the memory we assigned with shmop will persist after our program has exited, hogging resources. we do not want to litter our system with blocks of unused, reserved memory, piling up higher and higher with each successive run of our script!
freeing up shared memory blocks is done with shmop_delete. this function takes one argument: the pointer we created with shmop_open, and returns a boolean true on success.
note that shmop_delete destroys the memory block and frees up the space for other applications to use. we should only call it when we're completely done with using the memory.
the example we've been going over doesn't really do any error handling. this is a decision borne out of a desire for brevity, not delusional optimism. in real applications we should certainly do some error testing!
we used a path to a file as an argument for ftok; we should test that it exists. shmop_write will throw a value error if our memory block is opened read-only or we overwrite its size. that should be handled. if there's a problem reading data, shmop_read will return false. test for that.
if we open a shared memory block and then the script terminates before we call shmop_delete, the memory block still exists. if we then try to open that memory block again with shmop_open using the n mode, we will get the error:
PHP Warning: shmop_open(): Unable to attach or create shared memory segment "File exists" in /path/to/my/script on line <line number>
if our script is well-designed, this shouldn't happen. but, while developing and testing we may create these orphaned memory blocks. let's go over how to delete them.
the first step is to get the key of the memory block as a hex number. we do this by calling ftok as normal, and then converting the returned integer from base ten to base-16 like so:
$shm_key = ftok($shmop_file, $i); $shm_key_hex = "0x".base_convert($shm_key, 10, 16);
we do this because linux comes with a number of 'interprocess communication' tools that we can use to manage shared memory blocks, and they all use hexadecimal numbers for their keys.
the first command line tool we'll use is ipcs. we're going to use this to confirm that the shared memory block we want to delete does, in fact, exist.
the ipcs command, when run without arguments, will output all interprocess communication channels, including all shared memory blocks. we'll narrow down that output by using grep with the hexadecimal key we created above. for instance, if our shared memory block's key in hexadecimal is 0x33010024, we could do this:
ipcs | grep "0x33010024"
if we get a line of output, the memory block exists. if nothing is returned, it does not.
once we've confirm that a shared memory block exists, we can remove it with ipcrm
ipcrm --shmem-key 0x33010024
knowing how to inspect and clean up (without resorting to a restart) shared memory allows us to develop and experiment without turning our ram into a ghost town of abandoned blocks.
achieving concurrency in php using fork and shared memory does take some effort and knowledge (and the official manual is scant help). but it does work and, if you've made it through this article and the first installment on pcntl_fork, you should have a good base from which to start.
? this post originally appeared in the grant horwood technical blog
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!