Práce se sdílenou pamětí v PHP – rozbor možností

18. ledna 2005

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í.

<?php
# <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:

<?php
# <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.

<?php
# <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:

<?php
# <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.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *