ホームページ >バックエンド開発 >PHPチュートリアル >php: プロセスとの同時実行。 pt. shmop によるプロセス間通信

php: プロセスとの同時実行。 pt. shmop によるプロセス間通信

PHPz
PHPzオリジナル
2024-08-20 06:34:37572ブラウズ

php は、開発者がメモリなどについて通常考えるような言語ではありません。変数や関数をぶらぶらして、内部の「RAM に関するもの」をすべて理解してもらうだけです。それを変えてみましょう。

このシリーズの最初の部分では、子プロセスをフォークすることで多数のタスクを同時に実行できる php スクリプトを構築しました。かなりうまく機能しましたが、未解決の明らかな問題がありました。子プロセスが親プロセスにデータを送り返す方法がありませんでした。

今回は、php の「共有メモリ操作」である shmop を使用して、その問題を解決します。

php: concurrency with processes. pt. interprocess communication with shmop
シュモップ!

コンテンツ

  • 共有メモリに関する非常に短く、非常にオプションの説明
  • シュモップの基本的な高空飛行
  • 実際の実装
  • shmop_open でメモリブロックを開く
  • shmop_write を使用して...書き込みます
  • shmop_read によるメモリの読み取り
  • shmop_delete を使用してクリーンアップします
  • エラーの処理

共有メモリに関する非常に短く、極めてオプションの説明

新しいプロセスが開始されると、オペレーティング システムは使用するメモリのチャンクをそのプロセスに割り当てます。プロセスは、自分自身のメモリ以外のメモリに読み書きすることはできません。それは、セキュリティ上の悪夢となるからです。まったく合理的です。

これは、このシリーズのパート 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で共有メモリキーを作成する

上で説明したように、すべての共有メモリ ブロックは一意の整数キーによって識別され、メモリを割り当てるタスクに取り掛かる前に、そのキーを作成する必要があります。

正直に言うと、一意である限り、任意の整数を使用できますが、一般的に受け入れられている標準的な方法は、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 を使用します。これは、各子プロセスが結果を保存するための独自の共有メモリ ブロックを持つようにするためです。複数のプロセスが共有メモリに書き込もうとすると、悪いことが起こることに注意してください。ここでインデックスを使用すると、それを回避できます。

shmop_open でメモリ ブロックを開く

PHP の fopen や fwrite などのツールを使用してファイル アクセスを行ったことがある場合は、shmop の使用に非常に馴染みがあるでしょう。

shmop_open を使用して共有メモリ ブロックを開くことから始めましょう:

$shm_id = shmop_open($shm_key, 'n', 0755, 1024);

この関数は 4 つの引数を取ります:

  • key: ftok を使用して作成した一意のキー
  • mode: 必要なアクセスのタイプ。 fopen でファイルを開くときは、「読み取り」の r または書き込みの w のようなモードを使用します。 shmop_open のモードはそれに似ていますが、違いがあります。以下ですべてを説明します。
  • permissions: 8 進表記の共有メモリ ブロックの読み取り/書き込み/実行権限。共有メモリを処理するインターフェイスはファイル アクセスによく似ており、これにはアクセス許可が含まれます。 8 進数表記に自信がない場合は、使用できるファイル権限計算ツールがあります。この例では 0755 を使用していますが、値を厳しくすることもできます。
  • size: メモリ ブロックのサイズ (バイト単位)。この例では 1 メガバイトを割り当てていますが、これは明らかに過剰です。ただし、共有メモリ ブロックを上書きすると、値が切り捨てられることに注意してください。メモリ ブロックからそのサイズを超えるバイトを読み取ろうとすると、致命的なエラーが発生します。メモリに書き込むデータの正確なサイズがわからない場合は、必要なメモリ ブロックの大きさを多めに見積もったほうがよいでしょう。

ここで重要な点は、shmop_open を呼び出すと、そのキーにメモリ ブロックが存在しない場合に新しいメモリ ブロックが作成されるということです。これは fopen の動作に似ていますが、shmop_open の場合、この動作は渡す 'mode' 引数に依存します。

例に示すように、shmop_open は、メモリ ブロックを開くために使用されるモードに応じて、アクセス (読み取りまたは書き込み) に使用できるポインターを返します。

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

以上がphp: プロセスとの同時実行。 pt. shmop によるプロセス間通信の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。