Maison  >  Article  >  développement back-end  >  php : concurrence avec les processus. point. communication interprocessus avec shmop

php : concurrence avec les processus. point. communication interprocessus avec shmop

PHPz
PHPzoriginal
2024-08-20 06:34:37508parcourir

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.

php: concurrency with processes. pt. interprocess communication with shmop
shmop !

contenu

  • une explication très courte et extrêmement facultative de la mémoire partagée
  • un survol basique de shmop
  • une mise en œuvre réelle
  • ouverture d'un bloc mémoire avec shmop_open
  • utiliser shmop_write pour... écrire
  • lire la mémoire avec shmop_read
  • utiliser shmop_delete pour nettoyer
  • gestion des erreurs

une explication très courte et extrêmement facultative de la mémoire partagée

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.

un survol basique de shmop

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.

la mise en œuvre réelle

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);
}

créer une clé de mémoire partagée avec ftok

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.

ouvrir un bloc mémoire avec shmop_open

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 :

  • key : la clé unique que nous avons créée à l'aide de ftok.
  • mode : le type d'accès que nous souhaitons. lors de l'ouverture d'un fichier avec fopen, nous utilisons des modes comme r pour « lire » ou w pour écrire. Le mode de shmop_open est similaire à celui-ci, mais il y a des différences. nous passerons en revue tous ceux-ci ci-dessous.
  • permissions : les autorisations de lecture/écriture/exécution du bloc de mémoire partagée en notation octale. l'interface permettant de gérer la mémoire partagée est fortement analogue à l'accès aux fichiers, et cela inclut les autorisations. si vous n'êtes pas à l'aise avec la notation octale, vous pouvez utiliser des calculateurs d'autorisations de fichiers. nous utilisons 0755 dans cet exemple, mais vous voudrez peut-être le resserrer.
  • size : la taille du bloc mémoire en octets. dans l'exemple, nous attribuons un mégaoctet, ce qui est clairement excessif. cependant, notez que si nous écrasons notre bloc de mémoire partagée, la valeur sera tronquée. si nous essayons de lire plus d'octets d'un bloc mémoire que sa taille, une erreur fatale se produit. si nous ne sommes pas sûrs de la taille exacte des données que nous allons écrire en mémoire, il est préférable de surestimer la taille dont nous avons besoin pour notre bloc mémoire.

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.

php: concurrency with processes. pt. interprocess communication with shmop
007 are bad permissions for a spy

a little bit more about that 'mode' argument

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.

  • n : the 'n' stands for 'new' and is used when we want to create a new shared memory block. there does exist a mode c for 'create', but we are choosing to use n here because this mode will fail if we try to open a memory block that already exists. that's a safety feature! in fact, the docs state that using n to create a new shared memory block is 'useful' for 'security purposes'. the pointer returned from shmop_open using mode n is writeable.
  • a : this is for 'access'; ie. reading. do not confuse this with fopen's a mode, which is for 'append'. memory blocks opened with mode a are read-only.

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.

using shmop_write to... write.

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:

  • the pointer: the pointer returned from shmop_open. note that shmop_open must be called with a mode that allows writing (n in our example), otherwise attempts to use shmop_write will fail.
  • the value to write: the string to write to the shared memory block.
  • the offset: the number of bytes in memory to offset the start point of the write by. using the offset can allow us to append to a value already in the shared memory block, but doing this means keeping track of bytes written and can become unmanageable pretty quickly. in our example, we use the offset 0; we start writing at the beginning of the memory block.

shmop_write returns, as an integer, the number of bytes written.

a short note about shmop_close

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.

reading from shared memory

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:

  • recreate the key of the shared memory block
  • use the key to open the shared memory in 'access only' mode
  • read the data into a variable

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);
}

recreating the shmop key

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.

opening the shared memory

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.

reading from the 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 pointer we got from shmop_open.
  • the offset, in bytes. since we are reading the entirety of the memory block, starting at the beginning, this is 0 in our example (and will probably be for most real-life uses, as well)
  • the number of bytes to read. in most cases, the smart thing here is to just read the entire size of the block, in our example 1024 bytes.

the return type is a string. if there are errors reading, we get a boolean false.

deleting shared memory blocks

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.

handling errors

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.

php: concurrency with processes. pt. interprocess communication with shmop
i am asking you to do some error handling

fixing 'already exists' errors with shmop_open

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.

wrapping up

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!

Déclaration:
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