php は、開発者がメモリなどについて通常考えるような言語ではありません。変数や関数をぶらぶらして、内部の「RAM に関するもの」をすべて理解してもらうだけです。それを変えてみましょう。
このシリーズの最初の部分では、子プロセスをフォークすることで多数のタスクを同時に実行できる php スクリプトを構築しました。かなりうまく機能しましたが、未解決の明らかな問題がありました。子プロセスが親プロセスにデータを送り返す方法がありませんでした。
今回は、php の「共有メモリ操作」である shmop を使用して、その問題を解決します。
新しいプロセスが開始されると、オペレーティング システムは使用するメモリのチャンクをそのプロセスに割り当てます。プロセスは、自分自身のメモリ以外のメモリに読み書きすることはできません。それは、セキュリティ上の悪夢となるからです。まったく合理的です。
これは、このシリーズのパート 1 で pcntl_fork を使用して作成したプロセスを扱うときに問題を引き起こします。これは、子プロセスが相互に、またはその親と通信する簡単な方法がないことを意味するためです。子プロセスは作成時に親のメモリのコピーを取得するため、フォーク前に割り当てられたすべての変数にアクセスできますが、これらの変数への変更は子プロセスに限定されます。記憶の違いなど。親プロセスが読み取ることができる変数に子が書き込めるようにしたい場合、問題が発生します。
これには多数のソリューションがあり、それらはすべて「プロセス間通信」または ipc という一般的なカテゴリに分類されています。 PHP スクリプトに使用するのは「共有メモリ」です。
名前が示すように、共有メモリは、任意の数のプロセスがアクセスできるメモリのブロックです。共有メモリ ブロックは、(できれば)一意のキーによって識別されます。キーが何であるかを知っているプロセスは、そのメモリ ブロックにアクセスできます。これにより、子プロセスが親プロセスに報告できるようになります。子は共有メモリ ブロックにデータを書き込み、終了後に親が共有データを読み取ります。それは境界線にあるエレガントなソリューションです。
もちろん、これを行う際には回避する必要があるいくつかの難題があります。共有メモリ ブロックを識別するキーが一意であることを確認する必要があり、共有メモリ通信が 1 つのみで行われるように強制する必要があります。複数のプロセスがすべて同時に同じブロックに書き込もうとし、混乱が生じるのを避ける方法。これについてはすべて実装で説明します。
php には、共有メモリを処理するための豊富で堅牢な API があります。マニュアルには、「Shmop は、PHP で Unix 共有メモリ セグメントの読み取り、書き込み、作成、削除を可能にする、使いやすい関数セットです。」と記載されていますが、... それは間違いではありません。
共有メモリを使用するための主要な手順を見てみましょう:
一意のキーを作成します: すべての共有メモリはキーによって識別されます。共有メモリ ブロックのキーが何であるかを知っているプロセスは、そのキーにアクセスできます。従来、このキーはファイル システムからデータ (つまり、既存のファイルの i ノードから構築された値) を生成することによって作成されます。これは、ファイル システムがすべてのプロセスに共通のものであるためです。これには ftok を使用します。
キーを使用してメモリ ブロックを割り当てる: プロセスは、共有メモリ ブロックのキーを使用して、shmop_open を使用してブロックを「開く」ことができます。共有メモリ ブロックが存在しない場合は、共有メモリ ブロックが作成されます。 open 関数からの戻り値は、読み取りと書き込みに使用できるポインタです。以前に fopen と fwrite を使用したことがある場合は、このプロセスに精通しているはずです。
メモリ ブロックへのデータの書き込み: 共有メモリへの書き込みには、fwrite とよく似たインターフェイスがあります。ポインタが使用され、メモリに書き込む文字列が引数として渡されます。これを行う関数は shmop_write と呼ばれます。
メモリ ブロックからのデータの読み取り: 共有メモリからのデータの読み取りは、shmop_open からのポインタを使用して、shmop_read で実行されます。戻り値は文字列です。
キーを使用してメモリ ブロックを削除します: 共有メモリが不要になったら削除することが重要です。これは shmop_delete で行われます。
例から始めましょう。以下のコードは動作します。あまり興味がないか、博士課程に興味がない場合は、これをコピーして貼り付けて変更するだけで済みますが、そうでない人のために、すべてのシュモップ手順を確認して、その動作を説明します。そしてそれらがどのように機能するか。
// 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); }
上で説明したように、すべての共有メモリ ブロックは一意の整数キーによって識別され、メモリを割り当てるタスクに取り掛かる前に、そのキーを作成する必要があります。
正直に言うと、一意である限り、任意の整数を使用できますが、一般的に受け入れられている標準的な方法は、ftok を使用してファイルシステム内の既存のファイルを使用して整数を作成することです。基準点。
これを行う理論的根拠は非常に簡単です。プロセスはお互いについて何も知らないため、相互に合意された値を共有することが困難になります。ただし、システム上のすべてのプロセスに共通する数少ないものの 1 つは、ファイルシステムです。したがって、ftok。
既存のファイルへのパスに加えて、ftok は project_id 引数も受け取ります。ドキュメントによれば、これは「1 つの文字列」であり、他のプログラミング言語では「char」と呼ばれるものです。プロジェクト ID の目的は、共有メモリを作成する際の衝突を防ぐことです。 2 つの異なるベンダーによる 2 つのプロジェクトが両方とも ftok への引数として /etc/passwd を使用することを決定した場合、混乱が発生します。
かなり簡単な例を見てみましょう:
$shm_key = ftok('/usr/bin/php8.3', 'j'); print "shm_key = $shm_key";
ここでは、システム上に存在することがわかっているファイルへのフルパスを渡し、1 文字の project_id、'j' を指定しています。これを実行すると、print ステートメントは次のような出力を行います:
shm_key = 855706266
これは共有メモリの作成に使用するのに適した整数です!
システムでこのコードを実行すると、同じ引数を使用したとしても、ほぼ確実に異なる戻り値が返されます。これは、内部で ftok がファイルの i ノードを使用し、それがシステムごとに異なるためです。
何らかの理由で、存在しないファイルを ftok に渡すと、警告が表示されます。
PHP Warning: ftok(): ftok() failed - No such file or directory in <our script> on line <the line> shm_key = -1
これは単なる警告であり、ftok は先にチャージして値 -1 を与えるため、将来的に問題が発生することに注意してください。気をつけてください。
ここで、20 行目の ftok の呼び出しをもう一度見てみましょう。
$shm_key = ftok($shmop_file, $i);
ここでは、$shm_key に設定したファイルのパス、この場合は /usr/bin/php8.3、システム上に存在することがわかっているファイルのパスを ftok に渡しています。
project_id には、ループする配列のインデックスである $i を使用します。これは、各子プロセスが結果を保存するための独自の共有メモリ ブロックを持つようにするためです。複数のプロセスが共有メモリに書き込もうとすると、悪いことが起こることに注意してください。ここでインデックスを使用すると、それを回避できます。
PHP の fopen や fwrite などのツールを使用してファイル アクセスを行ったことがある場合は、shmop の使用に非常に馴染みがあるでしょう。
shmop_open を使用して共有メモリ ブロックを開くことから始めましょう:
$shm_id = shmop_open($shm_key, 'n', 0755, 1024);
この関数は 4 つの引数を取ります:
ここで重要な点は、shmop_open を呼び出すと、そのキーにメモリ ブロックが存在しない場合に新しいメモリ ブロックが作成されるということです。これは fopen の動作に似ていますが、shmop_open の場合、この動作は渡す 'mode' 引数に依存します。
例に示すように、shmop_open は、メモリ ブロックを開くために使用されるモードに応じて、アクセス (読み取りまたは書き込み) に使用できるポインターを返します。
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
以上がphp: プロセスとの同時実行。 pt. shmop によるプロセス間通信の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。