php는 개발자가 일반적으로 메모리 같은 것에 대해 생각하는 종류의 언어가 아닙니다. 우리는 변수와 함수를 둘러보고 내부가 우리를 위해 모든 '램 관련'을 알아내도록 합니다. 바꾸자.
이 시리즈의 첫 번째 부분에서는 하위 프로세스를 포크하여 여러 작업을 동시에 실행할 수 있는 PHP 스크립트를 구축했습니다. 꽤 잘 작동했지만 해결되지 않은 눈에 띄는 문제가 있었습니다. 즉, 하위 프로세스가 상위 프로세스로 데이터를 다시 보낼 수 있는 방법이 없었습니다.
이번 기사에서는 PHP의 "공유 메모리 작업"인 shmop을 사용하여 이 문제를 해결해 보겠습니다.
새 프로세스가 시작되면 운영 체제는 사용할 메모리 덩어리를 할당합니다. 프로세스는 자신의 것이 아닌 메모리를 읽거나 쓸 수 없습니다. 왜냐하면 이는 보안상 악몽이 될 것이기 때문입니다. 완벽하게 합리적입니다.
이 시리즈의 1부에서 pcntl_fork로 생성한 프로세스를 처리할 때 문제가 발생합니다. 왜냐하면 하위 프로세스가 서로 또는 상위 프로세스와 통신할 수 있는 쉬운 방법이 없기 때문입니다. 하위 프로세스는 생성될 때 상위 메모리의 복사본을 가져오므로 포크 이전에 할당된 모든 변수에 액세스할 수 있지만 이러한 변수에 대한 모든 변경 사항은 하위 프로세스로 제한됩니다. 다른 기억과 그 모든 것. 부모 프로세스가 읽을 수 있는 변수에 자식이 쓸 수 있기를 원한다면 문제가 있는 것입니다.
이를 위한 다양한 솔루션이 있으며 모두 '프로세스 간 통신' 또는 IPC라는 일반 범주로 그룹화되어 있습니다. PHP 스크립트에 사용할 것은 '공유 메모리'입니다.
이름에서 알 수 있듯이 공유 메모리는 임의의 수의 프로세스가 액세스할 수 있는 메모리 블록입니다. 공유 메모리 블록은 (희망적으로) 고유 키로 식별됩니다. 키가 무엇인지 아는 모든 프로세스는 해당 메모리 블록에 액세스할 수 있습니다. 이를 통해 하위 프로세스가 상위 프로세스에 다시 보고할 수 있습니다. 자식은 공유 메모리 블록에 데이터를 쓰고, 종료된 후에는 부모가 공유 데이터를 읽습니다. 경계선에 있는 우아한 솔루션입니다.
물론 이 작업을 수행할 때 피해야 할 몇 가지 문제가 있습니다. 공유 메모리 블록을 식별하는 키가 고유한지 확인해야 하며 공유 메모리 통신이 하나만 진행되도록 강제해야 합니다. 여러 프로세스가 동시에 동일한 블록에 쓰려고 시도하여 혼란을 일으키는 것을 방지하는 방법입니다. 구현 과정에서 이 모든 내용을 다룰 것입니다.
php에는 공유 메모리를 처리하기 위한 풍부하고 강력한 API가 있습니다. 매뉴얼에는 "Shmop은 PHP가 Unix 공유 메모리 세그먼트를 읽고, 쓰고, 생성하고 삭제할 수 있도록 하는 사용하기 쉬운 함수 세트입니다."라고 나와 있는데... 틀린 말이 아닙니다.
공유 메모리 사용의 핵심 단계를 살펴보겠습니다.
고유 키 생성: 모든 공유 메모리는 키로 식별됩니다. 공유 메모리 블록의 키가 무엇인지 알고 있는 모든 프로세스는 이에 액세스할 수 있습니다. 전통적으로 이 키는 파일 시스템에서 데이터(즉, 기존 파일의 inode에서 구축된 값)를 생성하여 생성됩니다. 파일 시스템은 모든 프로세스가 공통적으로 갖는 것이기 때문입니다. 이를 위해 ftok를 사용하겠습니다.
키를 사용하여 메모리 블록 할당: 프로세스는 shmop_open을 사용하여 공유 메모리 블록의 키를 '열기' 위해 사용할 수 있습니다. 공유 메모리 블록이 없으면 이를 생성합니다. open 함수의 반환 값은 읽고 쓰는 데 사용할 수 있는 포인터입니다. 이전에 fopen 및 fwrite를 사용해 본 적이 있다면 이 프로세스가 익숙할 것입니다.
메모리 블록에 데이터 쓰기: 공유 메모리에 쓰기는 fwrite와 매우 유사한 인터페이스를 갖습니다. 포인터가 사용되며 메모리에 쓸 문자열이 인수로 전달됩니다. 이를 수행하는 함수를 shmop_write라고 합니다.
메모리 블록에서 데이터 읽기: 공유 메모리에서 데이터 읽기는 shmop_read로 수행되며 다시 shmop_open의 포인터를 사용합니다. 반환 값은 문자열입니다.
키를 사용하여 메모리 블록 삭제: 더 이상 필요하지 않은 공유 메모리를 삭제하는 것이 중요합니다. 이는 shmop_delete로 수행됩니다.
예제부터 시작해 보겠습니다. 아래 코드는 작동하며, 충분히 궁금하지 않거나 간단히 설명할 수 있는 경우 복사하여 붙여넣고 수정하면 됩니다. 하지만 다른 모든 사람들을 위해 모든 shmop 단계를 살펴보고 수행하는 작업을 설명하겠습니다. 작동 원리를 알아보세요.
// 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.
기존 파일의 경로 외에도 ftok은 project_id 인수도 사용합니다. 문서에 따르면 이것은 다른 모든 프로그래밍 언어의 사람들이 'char'라고 부르는 '하나의 문자열'입니다. 프로젝트 ID의 목적은 공유 메모리 생성 시 충돌을 방지하는 것입니다. 두 개의 개별 공급업체가 수행한 두 프로젝트가 모두 ftok에 대한 인수로 /etc/passwd를 사용하기로 결정하면 혼란이 뒤따를 것입니다.
매우 간단한 예를 살펴보겠습니다.
$shm_key = ftok('/usr/bin/php8.3', 'j'); print "shm_key = $shm_key";
여기서는 시스템에 존재하는 것으로 알려진 파일의 전체 경로를 전달하고 한 문자의 project_id인 'j'를 제공합니다. 이것을 실행하면 print 문은 다음과 같은 결과를 출력할 것입니다:
shm_key = 855706266
공유 메모리를 생성하는 데 사용하기에 좋은 정수입니다!
이 코드를 시스템에서 실행하면 동일한 인수를 사용하더라도 거의 확실하게 다른 반환 값을 얻게 됩니다. 이는 내부적으로 ftok이 파일의 inode를 사용하고 시스템마다 다르기 때문입니다.
어떤 이유로 없는 파일을 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에 설정한 파일 경로를 ftok에 전달했습니다. 이 경우 /usr/bin/php8.3은 시스템에 존재하는 파일입니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!