php bukanlah jenis bahasa yang biasanya difikirkan oleh pembangun tentang perkara seperti ingatan. kita hanya semacam anduh sekitar pembolehubah dan fungsi dan biarkan dalaman memikirkan semua itu 'barangan ram' untuk kita. mari ubah itu.
dalam bahagian pertama siri ini, kami membina skrip php yang dapat menjalankan beberapa tugas secara serentak dengan memotong proses anak. ia berfungsi dengan baik, tetapi terdapat masalah yang ketara dan tidak ditangani: tiada cara untuk proses kanak-kanak tersebut menghantar data kembali ke proses induk.
dalam ansuran ini, kami akan menyelesaikan isu itu dengan menggunakan shmop, "operasi memori kongsi" php.
apabila proses baharu bermula, sistem pengendalian memberikannya sebahagian memori untuk digunakan. proses tidak boleh membaca atau menulis ke ingatan yang bukan milik mereka kerana, tentu saja, itu akan menjadi mimpi ngeri keselamatan. sangat munasabah.
ini menimbulkan isu untuk kami apabila berurusan dengan proses yang kami buat dengan pcntl_fork dalam bahagian satu siri ini, walau bagaimanapun, kerana ini bermakna tiada cara mudah untuk proses kanak-kanak berkomunikasi antara satu sama lain atau ibu bapa mereka. proses anak akan mendapat salinan ingatan ibu bapa mereka apabila ia dibuat supaya semua pembolehubah yang ditetapkan sebelum garpu boleh diakses, tetapi sebarang perubahan pada pembolehubah ini akan terhad kepada proses anak. memori yang berbeza dan semua itu. jika kita mahu kanak-kanak itu boleh menulis kepada pembolehubah yang boleh dibaca oleh proses ibu bapa, kita mempunyai masalah.
terdapat beberapa penyelesaian untuk ini, semuanya dikumpulkan di bawah kategori umum 'komunikasi antara proses' atau ipc. yang kami akan gunakan untuk skrip php kami ialah 'memori kongsi'.
seperti namanya, memori kongsi ialah blok memori yang boleh diakses oleh bilangan proses yang sewenang-wenangnya. blok memori yang dikongsi dikenal pasti oleh kunci unik (mudah-mudahan). sebarang proses yang mengetahui kuncinya boleh mengakses blok memori itu. ini membolehkan proses kanak-kanak melaporkan kembali kepada proses induk mereka; kanak-kanak akan menulis data ke blok memori yang dikongsi dan, selepas ia berhenti, ibu bapa akan membaca data yang dikongsi. ia adalah penyelesaian elegan sempadan.
sudah tentu, terdapat beberapa senapang kaki yang perlu kita elakkan apabila melakukan ini: kita perlu memastikan bahawa kunci yang mengenal pasti blok memori dikongsi adalah unik, dan kita perlu menguatkuasakan bahawa komunikasi memori dikongsi hanya berjalan satu cara untuk mengelakkan pelbagai proses semua cuba menulis ke blok yang sama pada masa yang sama dan menyebabkan kekacauan. kami akan merangkumi semua ini dalam pelaksanaan.
php mempunyai api yang kaya dan mantap untuk menangani memori bersama. manual menyatakan "Shmop ialah set fungsi yang mudah digunakan yang membolehkan PHP membaca, menulis, mencipta dan memadamkan segmen memori kongsi Unix", dan... tidak salah.
mari lihat langkah teras untuk menggunakan memori kongsi:
cipta kunci unik: semua memori yang dikongsi dikenal pasti oleh kunci. sebarang proses yang mengetahui kunci blok memori yang dikongsi boleh mengaksesnya. secara tradisinya, kunci ini dicipta dengan menjana data daripada sistem fail (iaitu nilai yang dibina daripada inod fail sedia ada) kerana sistem fail ialah sesuatu yang mempunyai persamaan pada semua proses. kami akan menggunakan ftok untuk ini.
tetapkan blok memori menggunakan kekunci: proses boleh menggunakan kunci blok memori kongsi untuk 'membuka' menggunakan shmop_open. jika blok memori yang dikongsi tidak wujud, ini menciptanya. nilai pulangan daripada fungsi terbuka ialah penunjuk yang boleh digunakan untuk membaca dan menulis. jika anda pernah menggunakan fopen dan fwrite sebelum ini, proses ini sepatutnya biasa.
tulis data ke blok memori: menulis ke memori kongsi mempunyai antara muka yang hampir sama seperti fwrite. penunjuk digunakan, dan rentetan untuk menulis ke ingatan diluluskan sebagai hujah. fungsi untuk melakukan ini dipanggil shmop_write.
baca data daripada blok memori: membaca data daripada memori kongsi dilakukan dengan shmop_read, sekali lagi menggunakan penuding daripada shmop_open. nilai pulangan ialah rentetan.
padamkan blok memori menggunakan kekunci: memadamkan memori kongsi selepas ia tidak diperlukan lagi adalah penting. ini dilakukan dengan shmop_delete.
mari kita mulakan dengan contoh. kod di bawah berfungsi dan, jika anda cukup tidak ingin tahu atau jenis tl;dr, anda boleh salin-tampal-ubah suai ini, tetapi untuk orang lain, kami akan menyemak semua langkah shmop dan menerangkan perkara yang mereka lakukan dan cara ia berfungsi.
// 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); }
seperti yang kita bincangkan di atas, semua blok memori yang dikongsi dikenal pasti oleh kunci integer yang unik dan sebelum kita boleh turun ke tugas untuk memberikan memori, kita telah mencipta kunci itu.
sejujurnya, kita boleh menggunakan mana-mana integer yang kita mahu, asalkan ia unik, namun cara kanonik yang diterima umum untuk melakukan ini adalah dengan menggunakan ftok untuk mencipta integer menggunakan fail sedia ada dalam sistem fail sebagai titik rujukan.
rasional untuk melakukan ini agak mudah. proses tidak mengetahui apa-apa tentang satu sama lain, yang menyukarkan mereka untuk berkongsi nilai yang dipersetujui bersama. salah satu daripada beberapa perkara yang semua proses pada sistem lakukan mempunyai persamaan, walaupun, ialah sistem fail. oleh itu, ftok.
selain laluan ke fail sedia ada, ftok juga mengambil argumen project_id. ini, menurut dokumen, 'satu rentetan aksara', apa yang orang dalam setiap bahasa pengaturcaraan lain akan panggil 'char'. tujuan id projek adalah untuk mengelakkan perlanggaran semasa mencipta memori bersama. jika dua projek oleh dua vendor berasingan kedua-duanya memutuskan untuk menggunakan /etc/passwd sebagai hujah mereka kepada ftok, huru-hara akan berlaku.
mari kita lihat contoh yang agak mudah:
$shm_key = ftok('/usr/bin/php8.3', 'j'); print "shm_key = $shm_key";
di sini kami menghantar laluan penuh ke fail yang kami tahu wujud pada sistem dan menyediakan project_id satu aksara, 'j'. jika kita menjalankan ini, pernyataan cetakan akan mengeluarkan sesuatu seperti:
shm_key = 855706266
itu adalah integer yang bagus untuk digunakan untuk mencipta memori kongsi kami!
jika anda menjalankan kod ini pada sistem anda, anda hampir pasti akan mendapat nilai pulangan yang berbeza, walaupun anda telah menggunakan hujah yang sama. ini kerana, di bawah hud, ftok menggunakan inod fail, dan itu berbeza dari sistem ke sistem.
jika, atas sebab tertentu, kami menghantar ke ftok fail yang tidak wujud, kami mendapat amaran.
PHP Warning: ftok(): ftok() failed - No such file or directory in <our script> on line <the line> shm_key = -1
perhatikan bahawa ini hanyalah amaran dan ftok akan mengecaj ke hadapan dan memberi kita nilai -1, yang akan mengakibatkan masalah di jalan raya. berhati-hati.
sekarang, mari kita semak semula panggilan kami ke ftok di talian 20:
$shm_key = ftok($shmop_file, $i);
di sini kami telah melepasi ftok laluan fail yang telah kami tetapkan dalam $shm_key, dalam kes ini /usr/bin/php8.3, fail yang kami tahu wujud pada sistem.
untuk project_id kami, kami menggunakan $i, indeks tatasusunan yang kami gelungkan. kami melakukan ini supaya setiap proses anak kami mempunyai blok memori dikongsi sendiri untuk menyimpan hasilnya. ingat bahawa jika lebih daripada satu proses cuba menulis ke memori bersama, Perkara Buruk berlaku. menggunakan indeks di sini membantu kami mengelakkannya.
jika anda pernah melakukan akses fail dengan alatan seperti php's fopen dan fwrite, maka penggunaan shmop akan menjadi sangat biasa.
mari mulakan dengan membuka blok memori kongsi dengan shmop_open:
$shm_id = shmop_open($shm_key, 'n', 0755, 1024);
fungsi ini memerlukan empat hujah:
nota penting di sini ialah memanggil shmop_open akan mencipta blok memori baharu jika satu pada kekunci itu belum wujud. ini serupa dengan cara fopen berkelakuan, tetapi dengan shmop_open kelakuan ini bergantung pada hujah 'mod' yang kami lalui.
seperti yang ditunjukkan dalam contoh, shmop_open mengembalikan penunjuk yang boleh digunakan untuk akses: membaca atau menulis, bergantung pada mod yang digunakan untuk membuka blok memori.
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
Atas ialah kandungan terperinci php: selaras dengan proses. pt. komunikasi antara proses dengan shmop. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!