Heim > Artikel > Backend-Entwicklung > PHP: Parallelität mit Prozessen. pt. Interprozesskommunikation mit Shmop
PHP ist nicht die Art von Sprache, in der Entwickler normalerweise über Dinge wie Speicher nachdenken. Wir schleudern einfach Variablen und Funktionen herum und lassen die Interna den ganzen „RAM-Kram“ für uns herausfinden. Lasst uns das ändern.
Im ersten Teil dieser Serie haben wir ein PHP-Skript erstellt, das durch die Verzweigung untergeordneter Prozesse eine Reihe von Aufgaben gleichzeitig ausführen konnte. Es hat ziemlich gut funktioniert, aber es gab ein eklatantes, ungelöstes Problem: Es gab keine Möglichkeit für diese untergeordneten Prozesse, Daten an den übergeordneten Prozess zurückzusenden.
In dieser Folge werden wir dieses Problem lösen, indem wir shmop, PHPs „Shared-Memory-Operationen“, verwenden.
Wenn ein neuer Prozess startet, weist ihm das Betriebssystem einen Teil des Speichers zur Nutzung zu. Prozesse können nicht in Speicher lesen oder schreiben, der nicht ihr eigener ist, denn das wäre ein Sicherheitsalptraum. vollkommen vernünftig.
Dies stellt jedoch ein Problem für uns dar, wenn wir mit den Prozessen umgehen, die wir im ersten Teil dieser Serie mit pcntl_fork erstellt haben, da es bedeutet, dass es für die untergeordneten Prozesse keine einfache Möglichkeit gibt, miteinander oder mit ihren übergeordneten Prozessen zu kommunizieren. Untergeordnete Prozesse erhalten bei ihrer Erstellung eine Kopie des Speichers ihrer übergeordneten Prozesse, sodass auf alle vor der Verzweigung zugewiesenen Variablen zugegriffen werden kann. Änderungen an diesen Variablen sind jedoch auf den untergeordneten Prozess beschränkt. andere Erinnerung und so. Wenn wir möchten, dass das untergeordnete Element in eine Variable schreiben kann, die der übergeordnete Prozess lesen kann, haben wir ein Problem.
Hierfür gibt es eine Reihe von Lösungen, die alle unter der allgemeinen Kategorie „Interprozesskommunikation“ oder IPC zusammengefasst sind. Das, was wir für unser PHP-Skript verwenden werden, ist „Shared Memory“.
Wie der Name schon sagt, ist Shared Memory ein Speicherblock, auf den eine beliebige Anzahl von Prozessen zugreifen kann. Gemeinsam genutzte Speicherblöcke werden durch einen (hoffentlich) eindeutigen Schlüssel identifiziert. Jeder Prozess, der den Schlüssel kennt, kann auf diesen Speicherblock zugreifen. Dies ermöglicht es einem untergeordneten Prozess, dem übergeordneten Prozess Bericht zu erstatten. Das Kind schreibt Daten in einen gemeinsam genutzten Speicherblock und nach dem Beenden liest das Elternteil die gemeinsam genutzten Daten. Es ist eine grenzwertig elegante Lösung.
Natürlich müssen wir dabei ein paar Tricks vermeiden: Wir müssen sicherstellen, dass der Schlüssel, der einen Shared-Memory-Block identifiziert, eindeutig ist, und wir müssen erzwingen, dass die Shared-Memory-Kommunikation nur einmal erfolgt So vermeiden Sie, dass mehrere Prozesse gleichzeitig versuchen, in denselben Block zu schreiben, und dadurch ein Durcheinander verursachen. Das alles werden wir in der Umsetzung abdecken.
php verfügt über eine umfangreiche und robuste API für den Umgang mit Shared Memory. Im Handbuch heißt es: „Shmop ist ein benutzerfreundlicher Funktionssatz, der es PHP ermöglicht, gemeinsam genutzte Unix-Speichersegmente zu lesen, zu schreiben, zu erstellen und zu löschen“, und... das ist nicht falsch.
Sehen wir uns die wichtigsten Schritte zur Verwendung von Shared Memory an:
eindeutigen Schlüssel erstellen: Der gesamte gemeinsam genutzte Speicher wird durch einen Schlüssel identifiziert. Jeder Prozess, der den Schlüssel eines gemeinsam genutzten Speicherblocks kennt, kann darauf zugreifen. Traditionell wird dieser Schlüssel erstellt, indem Daten aus dem Dateisystem generiert werden (d. h. ein Wert, der aus dem Inode einer vorhandenen Datei erstellt wird), da das Dateisystem etwas ist, das alle Prozesse gemeinsam haben. Wir werden dafür ftok verwenden.
Speicherblock mit dem Schlüssel zuweisen: Ein Prozess kann den Schlüssel eines gemeinsam genutzten Speicherblocks verwenden, um ihn mit shmop_open zu „öffnen“. Wenn der Shared-Memory-Block nicht vorhanden ist, wird er erstellt. Der Rückgabewert der Open-Funktion ist ein Zeiger, der zum Lesen und Schreiben verwendet werden kann. Wenn Sie schon einmal fopen und fwrite verwendet haben, sollte Ihnen dieser Vorgang bekannt sein.
Daten in den Speicherblock schreiben: Das Schreiben in den gemeinsam genutzten Speicher hat eine sehr ähnliche Schnittstelle wie fwrite. Der Zeiger wird verwendet und die in den Speicher zu schreibende Zeichenfolge wird als Argument übergeben. Die Funktion dazu heißt shmop_write.
Daten aus dem Speicherblock lesen: Das Lesen von Daten aus dem gemeinsam genutzten Speicher erfolgt mit shmop_read, wiederum unter Verwendung des Zeigers von shmop_open. Der Rückgabewert ist ein String.
Speicherblock mit der Taste löschen: Es ist wichtig, den gemeinsam genutzten Speicher zu löschen, nachdem er nicht mehr benötigt wird. Dies geschieht mit shmop_delete.
Beginnen wir mit einem Beispiel. Der folgende Code funktioniert, und wenn Sie nicht so neugierig sind oder ein Tl;dr-Typ sind, können Sie ihn einfach kopieren, einfügen und ändern, aber für alle anderen gehen wir alle Shmop-Schritte durch und erklären, was sie bewirken und wie sie funktionieren.
// 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); }
Wie wir oben beschrieben haben, werden alle gemeinsam genutzten Speicherblöcke durch einen eindeutigen ganzzahligen Schlüssel identifiziert und bevor wir mit der Speicherzuweisung beginnen können, müssen wir diesen Schlüssel erstellen.
Ehrlich gesagt können wir jede beliebige Ganzzahl verwenden, solange sie eindeutig ist. Der allgemein akzeptierte, kanonische Weg, dies zu tun, besteht jedoch darin, mit ftok eine Ganzzahl unter Verwendung einer vorhandenen Datei im Dateisystem als zu erstellen Bezugspunkt.
Der Grund dafür ist ziemlich einfach. Prozesse wissen nichts voneinander, was es für sie schwierig macht, einen gemeinsam vereinbarten Wert zu teilen. Eines der wenigen Dinge, die alle Prozesse auf einem System tun jedoch gemeinsam haben, ist das Dateisystem. daher ftok.
Zusätzlich zum Pfad zu einer vorhandenen Datei akzeptiert ftok auch ein project_id-Argument. Dies ist den Dokumenten zufolge eine „Ein-Zeichen-Zeichenfolge“, was Menschen in jeder anderen Programmiersprache als „char“ bezeichnen würden. Der Zweck der Projekt-ID besteht darin, Kollisionen beim Erstellen von Shared Memory zu verhindern. Wenn sich zwei Projekte von zwei verschiedenen Anbietern dazu entschließen würden, /etc/passwd als Argument für ftok zu verwenden, würde Chaos entstehen.
Sehen wir uns ein ziemlich einfaches Beispiel an:
$shm_key = ftok('/usr/bin/php8.3', 'j'); print "shm_key = $shm_key";
Hier übergeben wir den vollständigen Pfad zu einer Datei, von der wir wissen, dass sie auf dem System vorhanden ist, und stellen eine einstellige Projekt-ID „j“ bereit. Wenn wir dies ausführen, gibt die print-Anweisung etwa Folgendes aus:
shm_key = 855706266
Das ist eine gute Ganzzahl zum Erstellen unseres gemeinsamen Gedächtnisses!
Wenn Sie diesen Code auf Ihrem System ausführen, erhalten Sie mit ziemlicher Sicherheit einen anderen Rückgabewert, auch wenn Sie dieselben Argumente verwendet haben. Dies liegt daran, dass FTOK unter der Haube den Inode der Datei verwendet, und das ist von System zu System unterschiedlich.
Wenn wir aus irgendeinem Grund eine Datei an ftok übergeben, die nicht existiert, erhalten wir eine Warnung.
PHP Warning: ftok(): ftok() failed - No such file or directory in <our script> on line <the line> shm_key = -1
Beachten Sie, dass dies nur eine Warnung ist und ftok vorausstürmt und uns einen Wert von -1 gibt, was später zu Problemen führen wird. Sei vorsichtig.
Jetzt lassen Sie uns unseren Aufruf an ftok in Zeile 20 noch einmal durchgehen:
$shm_key = ftok($shmop_file, $i);
hier haben wir ftok den Pfad der Datei übergeben, die wir in $shm_key festgelegt haben, in diesem Fall /usr/bin/php8.3, eine Datei, von der wir wissen, dass sie auf dem System existiert.
Für unsere Projekt-ID verwenden wir $i, den Index des Arrays, über das wir eine Schleife durchlaufen. Wir tun dies, damit jeder unserer untergeordneten Prozesse seinen eigenen gemeinsamen Speicherblock zum Speichern seiner Ergebnisse hat. Denken Sie daran, dass schlimme Dinge passieren, wenn mehr als ein Prozess versucht, in den gemeinsam genutzten Speicher zu schreiben. Die Verwendung des Index hier hilft uns, dies zu vermeiden.
Wenn Sie jemals Dateizugriff mit Tools wie fopen und fwrite von PHP durchgeführt haben, wird Ihnen die Verwendung von shmop sehr vertraut sein.
Beginnen wir mit dem Öffnen eines Shared-Memory-Blocks mit shmop_open:
$shm_id = shmop_open($shm_key, 'n', 0755, 1024);
Diese Funktion benötigt vier Argumente:
Ein wichtiger Hinweis hier ist, dass der Aufruf von shmop_open einen neuen Speicherblock erstellt, falls noch keiner an diesem Schlüssel vorhanden ist. Dies ähnelt dem Verhalten von fopen, aber bei shmop_open hängt dieses Verhalten vom übergebenen „mode“-Argument ab.
Wie im Beispiel gezeigt, gibt shmop_open einen Zeiger zurück, der für den Zugriff verwendet werden kann: Lesen oder Schreiben, abhängig vom Modus, der zum Öffnen des Speicherblocks verwendet wird.
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
Das obige ist der detaillierte Inhalt vonPHP: Parallelität mit Prozessen. pt. Interprozesskommunikation mit Shmop. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!