Prßce se sdφlenou pam∞tφ v PHP - rozbor mo₧nostφ
Patrn∞ mnoh²m z vßs nßzev Φlßnku nic ne°φkß. D∙vod∙ je mnoho, mezi zßkladnφ vÜak pat°φ fakt, ₧e prßce se sdφlenou pam∞tφ (Shared Memory) se ve skriptech PHP p°φliÜ Φasto nevidφ, je spφÜe dominantou vyÜÜφch programovacφch jazyk∙. Ukß₧eme si proto, jak i pomocφ "obyΦejnΘho" PHP m∙₧eme vyu₧φt alespo≥ Φßst z toho, co nßm prßce se sdφlenou pam∞tφ nabφzφ.
Jak jsem ji₧ poznamenal v ·vodu, pro v∞tÜinu Φtenß°∙ bude uvedenΘ tΘma vcelku novΘ a neznßmΘ. Povφme si tedy na zaΦßtku pßr slov o tom, co vlastn∞ sdφlenß pam∞¥ je, co nßm nabφzφ a co nßm v koneΦnΘm d∙sledku i bere. Jeliko₧ si jsem v∞dom, ₧e teorie nebude zrovna pat°it mezi nejzajφmav∞jÜφ pasß₧e tohoto Φlßnku, pokusφm se vÜe podat pokud mo₧no jasn∞, struΦn∞ a srozumiteln∞.
Trocha teorie
Sdφlenß pam∞¥, v anglickΘm oznaΦenφ Shared Memory, je oznaΦenφ pro Φßst pam∞ti, kterß je urΦena pro uklßdßnφ a p°edßvßnφ dat mezi r∙zn²mi aplikacemi. P°i tom je jedno, zda se jednß o aplikaci, kterß je zkompilovanß nebo v ΦistΘm textu (typick² p°φklad jsou skripty napsanΘ v PHP, Perlu a podobn∞). Samotnß obsluha sdφlenΘ pam∞ti mß pak do jistΘ mφry n∞kolik spoleΦn²ch rys∙ s pracφ s b∞₧n²mi soubory, samoz°ejm∞ s tφm rozdφlem, ₧e se data neuklßdajφ na disk, ale p°φmo do pam∞ti serveru.
Data jsou v₧dy ulo₧ena pod jedineΦn²m identifikßtorem a my se ji₧ nadßle nemusφme tΘm∞° o nic starat. Zde je vÜak prßv∞ patrnΘ urΦitΘ omezenφ, proto₧e nem∙₧eme v PHP ovlivnit, kam p°esn∞ budou data v pam∞ti ulo₧ena, tuto mo₧nost nßm poskytujφ a₧ vyÜÜφ programovacφ jazyky.
Jak ji₧ z tohoto systΘmu uklßdßnφ dat vypl²vß, nezanedbatelnou v²hodou je n∞kolikanßsobn∞ vyÜÜφ rychlost oproti uklßdßnφ dat do b∞₧n²ch soubor∙. Na druhou stranu nesmφme zapomenout na to, ₧e data nejsou nikde fyzicky zapsßna a proto p°i p°φpadnΘm restartu Φi neoΦekßvanΘm pßdu serveru dojde k jejich nenßvratnΘ ztrßt∞.
Jak je na tom PHP
Pro prßci se sdφlenou pam∞tφ mßme v PHP dostupnΘ hned dv∞ sady funkcφ, kterΘ nßm umo₧≥ujφ provßd∞nφ zßkladnφch operacφ jako je Φtenφ a zßpis nebo vytvß°enφ a mazßnφ segment∙ pam∞ti.
Prvnφ z nich je sada SHMOP, kterß nßm zp°φstup≥uje veÜkerΘ v²Üe zmi≥ovanΘ operace. UrΦitΘ omezenφ t∞chto funkcφ spoΦφvß zejmΘna v jejich primitivnosti, a tak jsme omezeni nap°φklad ji₧ pouh²m typem uklßdan²ch dat, kdy m∙₧eme pracovat pouze s °et∞zci (string). Jak²koli pokus o ulo₧enφ jinΘho typu dat skonΦφ nekompromisn∞ chybou. Na druhou stranu velice v²znamn²m plusem pro tyto funkce je, ₧e dφky jejich jednoduchosti lze vcelku bez problΘm∙ p°istupovat k takto ulo₧en²m dat∙m i pomocφ program∙ vytvo°en²ch v jin²ch programovacφch jazycφch. Nask²tß se nßm zde vcelku zajφmavΘ °eÜenφ, jak zajistit komunikaci nap°φklad mezi skriptem PHP a aplikacφ vytvo°enou v C#...
Druhou mo₧nostφ je vyu₧itφ rozÜφ°enφ, kterΘ poskytuje rozhranφ k rodin∞ IPC funkcφ System V). Dφky t∞mto funkcφm m∙₧eme provßd∞t vÜemo₧nΘ operace se sdφlenou pam∞tφ, navφc vÜak umo₧≥uje prßci s meziprocesorov²mi zprßvami a nastavovßnφ takzvanΘho semaforu, kter² nßm pomßhß chrßnit naÜe data proti simultßnnφm p°φstup∙m. Oproti funkcφm °ady SHMOP mßme zde takΘ mo₧nost uklßdat vÜechny typy prom∞nn²ch (double, integer, string i array), co₧ je n∞kdy opravdu neocenitelnou v²hodou. Na tuto univerzßlnost vÜak doplatila do jistΘ mφry rychlost, kterß dφky vklßdan²m hlaviΦkßm je pon∞kud pomalejÜφ ne₧li u SHMOP (Na n∞kter²ch systΘmech m∙₧e pr² tento rozdφl Φinit a₧ 20 %.) Druhou, pro n∞koho mo₧nß nepodstatnou nev²hodou je, ₧e skripty vyu₧φvajφcφ t∞chto funkcφ nelze provozovat na systΘmech Windows.
Jak to funguje v praxi
Nynφ si ukß₧eme vyu₧itφ t∞chto funkcφ v praxi. P°ed samotnou pracφ se, prosφm, ujist∞te, ₧e mßte zkompilovßnu pot°ebnou podporu v PHP. Pro podporu funkcφ SHMOP to je parametr --enable-shmop, pro podporu System V to jsou --enable-sysvsem a --enable-sysvshm.
Vyu₧itφ funkcφ SHMOP
Pro nßzornou demonstraci si vytvo°φme dva skripty, kdy jeden provede zßpis dat a druh² naΦtenφ a zobrazenφ.
# <shmop_write.php>
$save_text="Zde je ulozeny retezec.";
// Vytvorime blok o velikosti 50b s pristupovymi pravy 644
$shmid=shmop_open(0xff3,"c",0644,50);
// Neco se nepovedlo a tak nastala chyba
if(!$shmid) die("Nepodarilo se vytvorit blok sdilene pameti.");
// Ulozime retezec do sdilene pameti
$shm_bytes_written=shmop_write($shmid,$save_text,0);
if($shm_bytes_written!=strlen($save_text))
echo "Retezec nebyl zapsan cely.";
// Uzavreme aktualni blok
shmop_close($shmid);
?>
Prvnφ zajφmavou funkcφ je shmop_open(int key, string flags, int mode, int size)
. Parametr "key" je typu integer (zadßvßme bu∩ v ÜestnßctkovΘ soustav∞ nebo desφtkovΘ) a udßvß ID segmentu sdφlenΘ pam∞ti, kter² slou₧φ k jednoznaΦnΘ identifikaci.
DalÜφ v po°adφ je parametr "flags", kter² m∙₧e nab²vat Φty° hodnot, a to c pro vytvo°enφ novΘho bloku, a pro Φtenφ z ji₧ existujφcφho segmentu sdφlenΘ pam∞ti a w znaΦφ p°φstup pro Φtenφ i zßpis. Poslednφ parametr n se pokusφ vytvo°it segment s dan²m "key" stejn∞ jako p°i pou₧itφ c, ovÜem s tφm rozdφlem, ₧e dojde k chyb∞, jestli₧e segment s dan²m ID ji₧ existuje (v p°φpad∞, ₧e bychom pou₧ili parametr c a dan² segment ji₧ p°itom existoval, funkce se ho pokusφ otev°φt pro Φtenφ a zßpis).
T°etφ parametr udßvß p°φstupovß prßva k danΘmu segmentu pam∞ti stejn²m zp∙sobem, jako se nastavujφ prßva u b∞₧n²ch soubor∙ (v osmiΦkovΘ soustav∞). KoneΦn∞ poslednφ v po°adφ je parametr "size", kter² °φkß, jak velk² segment pam∞ti si p°ejeme rezervovat pro pou₧itφ (uvßdφme hodnotu v bytech).
Jak ji₧ bylo °eΦeno v²Üe, v p°φpad∞, ₧e se nepoda°φ blok vytvo°it Φi otev°φt, vrßtφ funkce chybu. V opaΦnΘm p°φpad∞ se nßm do prom∞nnΘ $shmid ulo₧φ ID danΘ relace, kterΘ pou₧φvßme pro dalÜφ prßci.
Funkce shmop_write(int shmid, string data, int offset)
slou₧φ k zßpisu dat typu string do pam∞ti s poΦßteΦnφm offsetem zßpisu urΦen²m poslednφm parametrem "offset" (v naÜem p°φpad∞ nastaven na nulu). Prom∞nnß $shm_bytes_written obsahuje poΦet ·sp∞Ün∞ zapsan²ch byt∙, co₧ je u₧iteΦnΘ zejmΘna ke zp∞tnΘ kontrole, zda byl cel² °et∞zec ulo₧en. Pokud bychom toti₧ cht∞li ulo₧it °et∞zec o velikosti dvaceti byt∙ a p°itom by v danΘ relaci byl zarezervovßn blok o velikosti pouh²ch deseti byt∙, zapsala by se jen polovina °et∞zce, co₧ je celkem ne₧ßdoucφ.
Cel² ukßzkov² skript zakonΦuje p°φkaz shmop_close(int shmid)
, kterΘmu p°edßvßme pouze jedin² parametr, a to identifikßtor relace, kterou si p°ejeme uzav°φt. Po uzav°enφ jsou data v pam∞ti i nadßle ulo₧ena a lze je zφskat pomocφ dalÜφho skriptu:
# <shmop_read.php>
// Otevreme blok sdilene pameti
$shmid=shmop_open(0xff3,"a",0,0);
// Neco se nepovedlo a tak nastala chyba
if(!$shmid) die("Nepodarilo se otevrit blok sdilene pameti.");
// Nacteme retezec do sdilene pameti
$load_text=shmop_read ($shmid,0,50);
if(!$load_text) echo "Doslo k chybe pri nacitani dat.";
// Vypiseme nactena data
else echo "Nactena data: ".$load_text;
// Uzavreme aktualni segment
shmop_close($shmid);
?>
Zde si lze vÜimnout, ₧e na samotn²ch funkcφch se mnoho nezm∞nilo. Op∞t si otev°eme relaci pomocφ funkce shmop_open()
- tentokrßt ale chceme Φφst z ji₧ vytvo°enΘho segmentu, tak₧e pou₧ijeme parametr "a". Jeliko₧ jsou p°φstupovß prßva a velikost pam∞ti ji₧ nastaveny z d°φv∞jÜka a nelze tedy tyto parametry ovlivnit, uvedeme na mφsto t°etφho a ΦtvrtΘho parametru nulu ("0").
Funkce shmop_read(int shmid, int offset, int count)
zajiÜ¥uje naΦtenφ dat z urΦenΘho segmentu pam∞ti (pokud danß oblast n∞jakß data obsahuje), kterΘ jsou nßsledn∞ dostupnΘ v prom∞nnΘ $load_text. Na prvnφm mφst∞ op∞t uvedeme identifikßtor relace, na druhΘm mφst∞ je offset, kter² udßvß, od jakΘho mφsta se mß zaΦφt Φφst, a poslednφ parametr "count" °φkß, kolik byt∙ mß b²t p°eΦteno. Jak se sami m∙₧ete p°esv∞dΦit, d°φve ulo₧enß data se opravdu nikde neztratila a m∙₧eme si je nechat zobrazit.
Pokud chceme dan² segment pam∞ti trvale zruÜit, pou₧ijeme funkci shmop_delete(int shmid)
, kterß za°φdφ odstran∞nφ segmentu vΦetn∞ ulo₧en²ch dat.
IPC funkce System V
V p°φpad∞ vyu₧itφ rozhranφ k rodin∞ IPC funkcφ System V, dochßzφ k uklßdßnφ dat obdobn² zp∙sobem jako v p°edeÜlΘ ukßzce, nicmΘn∞ nejsme zde tolik omezeni typem uklßdan²ch dat.
# <shm_write.php>
$save=array("prvni","druha","treti","ctvrta","pata");
// Vytvorime blok o velikosti 100b s pristupovymi pravy 644
$shmid=shm_attach(98374,100,0644);
// Nastavime semafor pro dany segment
$semid=sem_get(98374,1);
// Neco se nepovedlo a tak nastala chyba
if(!$shmid||!$semid) die("Nastala chyba");
// Ukladame data
sem_acquire($semid);
$result=shm_put_var($shmid,2,$save);
if(!$result) echo "Retezec nebyl zapsan.";
sem_release($semid);
// Uzavreme aktualni segment
shm_detach($shmid);
?>
Jak je nßzorn∞ vid∞t, neb²t nastavovßnφ semaforu, byl by skript prakticky toto₧n², p°esto si ho op∞t popφÜeme krok za krokem. V prvnφ °ad∞ si op∞t vytvo°φme pracovnφ segment - v p°φpad∞, ₧e ji₧ segment s dan²m "key" existuje, otev°eme p°φsluÜnou relaci. Nenechte se mßst tφm, ₧e p°i opakovanΘm volßnφ funkce shm_attach(int key, int size, int mode)
vrßtφ rozdφln² identifikßtor $shmid, pracujete po°ßd se stejn²m segmentem pam∞ti. Poslednφ dva parametry nejsou povinnΘ a je tedy mo₧nΘ je vynechat. V tom p°φpad∞ budou pou₧ity v²chozφ hodnoty, tedy size=10000 a mode=0666.
Funkce sem_get(int key, int maxacc, int mode)
vytvo°φ, p°φpadn∞ otev°e ji₧ existujφcφ semafor pro dan² "key". Parametr "maxacc" udßvß, kolik proces∙ m∙₧e souΦasn∞ zφskat p°φstup k tomuto semaforu. V²chozφ hodnota je "1" (pokud v dob∞ vytvß°enφ Φi otevφrßnφ semaforu nenφ zaznamenßn n∞jak² dalÜφ p°φstup). Jako u p°edeÜlΘ funkce i zde platφ, ₧e poslednφ dva parametry nemusejφ b²t zadßny.
Dßle zavolßme funkci sem_acquire(int semid)
, kterß mß za ·kol zφskat semafor pro nßÜ proces. V p°φpad∞, ₧e nenφ semafor dostupn² (poΦet proces∙ vyΦerpal maximßlnφ poΦet povolen²ch proces∙ "maxacc"), tato funkce blokuje nßÜ proces a neumo₧nφ mu prßci s pam∞tφ. Takto je docφleno vcelku elegantnφho oÜet°enφ proti souΦasn²m p°φstup∙m k dat∙m.
Pomocφ funkce shm_put_var(int shmid, int datakey, mixed data)
provedeme samotnΘ ulo₧enφ dat. Prvnφ prom∞nnß urΦuje, pod jak²m identifikßtorem pracujeme, druhß prom∞nnß je klφΦ, se kter²m jsou data uklßdßna (m∙₧eme urΦit libovoln∞, nap°φklad "2"), a koneΦn∞ poslednφ parametr p°edßvß samotnß data libovolnΘho typu (v naÜem p°φpad∞ array).
Po ulo₧enφ dat funkce sem_release(int semid)
uvolnφ semafor, aby tak k danΘmu segmentu mohly p°istupovat dalÜφ procesy. Kdybychom tuto funkci nezavolali, byl by semafor i nadßle blokovßn a d°φve Φi pozd∞ji (po p°ekroΦenφ maximßlnφho poΦtu povolen²ch proces∙) by nebylo mo₧nΘ do pam∞ti zapisovat.
Poslednφ funkce v naÜem skriptu shm_detach(int shmid)
uzav°e spojenφ s dan²m $shmid. Data jsou v pam∞ti i nadßle ulo₧ena a m∙₧eme je zφskat pomocφ nßsledujφcφho skriptu:
# <shm_read.php>
// Otevreme blok
$shmid=shm_attach(98374,0,0);
// Nastavime semafor pro dany segment
$semid=sem_get(98374);
// Neco se nepovedlo a tak nastala chyba
if(!$shmid||!$semid) die("Nastala chyba");
// Nacteme data
sem_acquire($semid);
$result=shm_get_var($shmid,2);
sem_release($semid);
if(!$result) echo "Doslo k chybe pri nacitani dat.";
else
// Vypiseme data
for($i=0;$i<count($result);$i++) echo "$i: Obsah: $result[$i]<br />";
// Uzavreme aktualni segment
shm_detach($shmid);
?>
Tento skript ji₧ nebudu popisovat tak podrobn∞, na prvnφ pohled je stejn², jako p°edeÜl², a₧ na dva nepatrnΘ rozdφly. Prvnφm rozdφlem je, ₧e jsme ji₧ u otevφrßnφ segmentu nedefinovali p°φstupovß prßva a velikost segmentu, proto₧e p°i otevφrßnφ existujφcφho segmentu jsou tyto parametry ignorovßny.
Druh² rozdφl spoΦφvß v zßm∞n∞ funkce shm_put_var()
za shm_get_var(int shmid, int datakey)
. Zde dopl≥ujeme pouze dva parametry, a to sice identifikßtor relace na mφst∞ prvnφm, a klφΦ dat, kterΘ chceme naΦφst, na mφst∞ druhΘm. (Jak si tedy pozorn² Φtenß° zajistΘ vÜiml, objevuje se zde ji₧ zßkladnφ nßznak selekce dat a nemusφme naΦφtat celou pam∞¥ jako u funkcφ °ady SHMOP.) Jeliko₧ jsou extrahovanß data typu array, je samotn² v²pis realizovßn jednoduch²m cyklem. Nakonec op∞t uzav°eme relaci pomocφ funkce shm_detach(int shmid)
.
UrΦit∞ se ptßte, jak postupovat v p°φpad∞ pot°eby odstran∞nφ dat, p°φpadn∞ celΘho segmentu. Zde na nßs PHP pamatovalo o n∞co vφce a nabφzφ nßm hned dv∞ funkce, z nich₧ ka₧dß mß jinΘ pou₧itφ. Prvnφ je shm_remove_var(int shmid, int datakey)
, kterß umo₧≥uje odstranit ze sdφlenΘ pam∞ti data, je₧ byla ulo₧ena pod klφΦem "datakey". Po zavolßnφ tΘto funkce dojde k okam₧itΘmu odstran∞nφ dat a uvoln∞nφ mφsta v danΘm segmentu sdφlenΘ pam∞ti. Pokud bychom se rozhodli odstranit cel² segment pam∞ti vΦetn∞ v n∞m obsa₧en²ch dat, pou₧ijeme funkci shm_remove(int shmid)
, kterΘ staΦφ udat identifikßtor relace.
Prßce se sdφlenou pam∞tφ v PHP
Vyu₧itφ a prßce se sdφlenou pam∞ti ve skriptech PHP na platformßch UNIX a WIN32 pomocφ funkcφ SHMOP a rozhranφ k rodin∞ IPC funkcφ System V. Popis funkcφ a ukßzkovΘ p°φklady vΦetn∞ jednoduchΘ aplikace chatu. Tato sΘrie Φlßnk∙ dosud nebyla ukonΦena!
- Prßce se sdφlenou pam∞tφ v PHP - rozbor mo₧nostφ (prßv∞ Φtete)