Heim  >  Artikel  >  Backend-Entwicklung  >  PHP: Parallelität mit Prozessen. pt. Interprozesskommunikation mit Shmop

PHP: Parallelität mit Prozessen. pt. Interprozesskommunikation mit Shmop

PHPz
PHPzOriginal
2024-08-20 06:34:37526Durchsuche

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.

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

Inhalt

  • eine sehr kurze und äußerst optionale Erklärung des Shared Memory
  • ein einfacher Shmop-Überflug
  • eine tatsächliche Implementierung
  • Öffnen eines Speicherblocks mit shmop_open
  • shmop_write verwenden, um... zu schreiben
  • Speicher mit shmop_read lesen
  • Verwenden Sie shmop_delete zum Bereinigen
  • Fehlerbehandlung

eine sehr kurze und äußerst optionale Erklärung des Shared Memory

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.

ein einfacher Überflug von Shmop

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.

die eigentliche Umsetzung

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

Erstellen eines Shared-Memory-Schlüssels mit FTOK

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.

Öffnen eines Speicherblocks mit shmop_open

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:

  • Schlüssel: der eindeutige Schlüssel, den wir mit ftok erstellt haben.
  • Modus: die Art des Zugriffs, den wir wollen. Beim Öffnen einer Datei mit fopen verwenden wir Modi wie r für „Lesen“ oder w für Schreiben. Der Modus von shmop_open ist diesem ähnlich, es gibt jedoch Unterschiede. Wir gehen das alles weiter unten durch.
  • Berechtigungen: die Lese-/Schreib-/Ausführungsberechtigungen des gemeinsam genutzten Speicherblocks in Oktalschreibweise. Die Schnittstelle für den Umgang mit Shared Memory ähnelt stark dem Dateizugriff, und dazu gehören auch Berechtigungen. Wenn Sie mit der Oktalschreibweise nicht vertraut sind, können Sie Rechner für Dateiberechtigungen verwenden. In diesem Beispiel verwenden wir 0755, aber vielleicht möchten Sie die Zahl präzisieren.
  • Größe: die Größe des Speicherblocks in Bytes. Im Beispiel weisen wir ein Megabyte zu, was eindeutig übertrieben ist. Beachten Sie jedoch, dass der Wert abgeschnitten wird, wenn wir unseren gemeinsam genutzten Speicherblock überschreiben. Wenn wir versuchen, mehr Bytes aus einem Speicherblock zu lesen, als er groß ist, tritt ein schwerwiegender Fehler auf. Wenn wir uns über die genaue Größe der Daten, die wir in den Speicher schreiben werden, nicht sicher sind, ist es besser, die Größe unseres Speicherblocks zu überschätzen.

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.

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

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn