php: 프로세스와의 동시성. pt. shmop을 사용한 프로세스 간 통신

PHPz
풀어 주다: 2024-08-20 06:34:37
원래의
512명이 탐색했습니다.

php는 개발자가 일반적으로 메모리 같은 것에 대해 생각하는 종류의 언어가 아닙니다. 우리는 변수와 함수를 둘러보고 내부가 우리를 위해 모든 '램 관련'을 알아내도록 합니다. 바꾸자.

이 시리즈의 첫 번째 부분에서는 하위 프로세스를 포크하여 여러 작업을 동시에 실행할 수 있는 PHP 스크립트를 구축했습니다. 꽤 잘 작동했지만 해결되지 않은 눈에 띄는 문제가 있었습니다. 즉, 하위 프로세스가 상위 프로세스로 데이터를 다시 보낼 수 있는 방법이 없었습니다.

이번 기사에서는 PHP의 "공유 메모리 작업"인 shmop을 사용하여 이 문제를 해결해 보겠습니다.

php: concurrency with processes. pt. interprocess communication with shmop
쉬모프!

내용물

  • 공유 메모리에 대한 매우 짧고 선택적인 설명
  • shmop의 기본 비행
  • 실제 구현
  • shmop_open으로 메모리 블록 열기
  • shmop_write를 사용하여... 쓰기
  • shmop_read로 메모리 읽기
  • shmop_delete를 사용하여 정리
  • 오류 처리

공유 메모리에 대한 매우 짧고 선택적인 설명

새 프로세스가 시작되면 운영 체제는 사용할 메모리 덩어리를 할당합니다. 프로세스는 자신의 것이 아닌 메모리를 읽거나 쓸 수 없습니다. 왜냐하면 이는 보안상 악몽이 될 것이기 때문입니다. 완벽하게 합리적입니다.

이 시리즈의 1부에서 pcntl_fork로 생성한 프로세스를 처리할 때 문제가 발생합니다. 왜냐하면 하위 프로세스가 서로 또는 상위 프로세스와 통신할 수 있는 쉬운 방법이 없기 때문입니다. 하위 프로세스는 생성될 때 상위 메모리의 복사본을 가져오므로 포크 이전에 할당된 모든 변수에 액세스할 수 있지만 이러한 변수에 대한 모든 변경 사항은 하위 프로세스로 제한됩니다. 다른 기억과 그 모든 것. 부모 프로세스가 읽을 수 있는 변수에 자식이 쓸 수 있기를 원한다면 문제가 있는 것입니다.

이를 위한 다양한 솔루션이 있으며 모두 '프로세스 간 통신' 또는 IPC라는 일반 범주로 그룹화되어 있습니다. PHP 스크립트에 사용할 것은 '공유 메모리'입니다.

이름에서 알 수 있듯이 공유 메모리는 임의의 수의 프로세스가 액세스할 수 있는 메모리 블록입니다. 공유 메모리 블록은 (희망적으로) 고유 키로 식별됩니다. 키가 무엇인지 아는 모든 프로세스는 해당 메모리 블록에 액세스할 수 있습니다. 이를 통해 하위 프로세스가 상위 프로세스에 다시 보고할 수 있습니다. 자식은 공유 메모리 블록에 데이터를 쓰고, 종료된 후에는 부모가 공유 데이터를 읽습니다. 경계선에 있는 우아한 솔루션입니다.

물론 이 작업을 수행할 때 피해야 할 몇 가지 문제가 있습니다. 공유 메모리 블록을 식별하는 키가 고유한지 확인해야 하며 공유 메모리 통신이 하나만 진행되도록 강제해야 합니다. 여러 프로세스가 동시에 동일한 블록에 쓰려고 시도하여 혼란을 일으키는 것을 방지하는 방법입니다. 구현 과정에서 이 모든 내용을 다룰 것입니다.

shmop의 기본 비행

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.

기존 파일의 경로 외에도 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를 사용합니다. 우리는 각 하위 프로세스가 결과를 저장할 자체 공유 메모리 블록을 갖도록 이 작업을 수행하고 있습니다. 둘 이상의 프로세스가 공유 메모리에 쓰려고 하면 나쁜 일이 발생한다는 것을 기억하십시오. 여기서 색인을 사용하면 이를 방지하는 데 도움이 됩니다.

shmop_open으로 메모리 블록 열기

PHP의 fopen 및 fwrite와 같은 도구를 사용하여 파일 액세스를 수행한 적이 있다면 shmop을 사용하는 것이 매우 익숙할 것입니다.

shmop_open을 사용하여 공유 메모리 블록을 여는 것부터 시작해 보겠습니다.

$shm_id = shmop_open($shm_key, 'n', 0755, 1024);
로그인 후 복사

이 함수는 4개의 인수를 사용합니다:

  • key: ftok를 사용하여 생성한 고유 키
  • 모드: 우리가 원하는 액세스 유형입니다. fopen으로 파일을 열 때 '읽기'에는 r, 쓰기에는 w와 같은 모드를 사용합니다. shmop_open의 모드는 그것과 비슷하지만 차이점이 있습니다. 아래에서 모든 내용을 살펴보겠습니다.
  • 권한: 8진수 표기법으로 나타낸 공유 메모리 블록의 읽기/쓰기/실행 권한입니다. 공유 메모리를 처리하기 위한 인터페이스는 파일 액세스와 매우 유사하며 여기에는 권한이 포함됩니다. 8진수 표기법에 자신이 없다면 파일 권한 계산기를 사용할 수 있습니다. 이 예에서는 0755를 사용하고 있지만 더 엄격하게 설정할 수도 있습니다.
  • size: 메모리 블록의 크기(바이트)입니다. 이 예에서는 1MB를 할당하고 있는데 이는 확실히 과잉입니다. 그러나 공유 메모리 블록을 덮어쓰면 값이 잘립니다. 메모리 블록에서 크기보다 더 많은 바이트를 읽으려고 하면 치명적인 오류가 발생합니다. 메모리에 쓸 데이터의 정확한 크기를 확신할 수 없다면 메모리 블록의 크기를 과대평가하는 것이 좋습니다.

여기서 중요한 점은 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿