PHP

Garbage collection štala

nedelja, 18. marec 2007

Garbage collection v PHP 5 deluje sicer zelo v redu, pojavi pa se problem s štetjem referenc na objekt v določenem primeru. Teoretično naj bi PHP sprostil pomnilnik, ko na določen objekt ne kaže več nobena referenca, vendar temu ni tako v vseh primerih. Problem nastane, ko dva objekta kažeta eden na drugega, ponavadi se take zadeve zgodijo v parent / child odnosu.

Poglejmo si primer:

class parentObject {

protected $childObject;

function __construct() {
$this->childObject = new childObject($this);
}
}

class childObject {

protected $parentObject;

function __construct($parentObject) {
$this->parentObject = $parentObject;
}
}

$objekt = new parentObject();

Zadeva izgleda cisto legalna, ko ustvarimo nov objekt razreda parentObject, ta v svojem konstruktu ustvari otroka, objekt razreda childObject, da pa se otrok zaveda čigav je, mu starš kot parameter poda referenco nase, ki si jo otrok seveda zapomni. Vse lepo in prav, do tukaj. Kaj pa se zgodi, ko objekta ne potrebujemo več? Kaj ponavadi naredimo v takem primeru? Enostavno:

unset($objekt);

Gornja koda povzroči, da se izbriše referenca na objekt, garbage collector pa, glede na to, da referenc na objekt ni več, poskrbi za to, da se ustrezno sprosti tudi pomnilnik. No, v tem primeru ne. S klicem funkcije unset() smo sicer prekinili referenco $objekt, vendar referenca $this->parentObject v otrokovem objektu še kar obstaja. Zlomka, garbage collector ne naredi nič. Če te zadeve uporabljamo v kakšni dolgi zanki, seveda lahko sklepamo kaj se zgodi. Izvajanje skripte se prekine, ko se napolni ves dovoljeni pomnilnik.

Kako to rešiti? Našel sem samo eno rešitev, ki tudi ni tako zelo slaba. Objektoma dodamo metodo destroy():

class parentObject {

protected $childObject;

function __construct() {
$this->childObject = new childObject($this);
}

function destroy() {
$this->childObject->destroy();
unset($this->childObject);
}
}

class childObject {

protected $parentObject;

function __construct($parentObject) {
$this->parentObject = $parentObject;
}

function destroy() {
unset($this->parentObject)
}
}

$objekt = new parentObject();

In še, ko objekta ne potrebujemo več:

$objekt->destroy();
unset($objekt);

Kaj smo s tem naredili? Poklicali smo metodo destroy() starševega objekta, ta pokliče metodo destroy() otrokovega objekta, ki prekine referenco na svojega starša (kot je Ivan mamo zatajil), takoj zatem še starš prekine referenco na otroka (prav mu je Ivanu, jaz sem se pa tako trudila s kavo), no potem pa mi prekinemo še referenco na starša in vsemogočni garbage collector poskrbi, da se oba objekta razblineta v nič.

Problem rešen!

1.
hoho
19. marec 2007, 15:17

Kul. Za katero firmo pa delaš take advanced PHP stvari?

2.
20. marec 2007, 01:34

Petrov & Petrov

3.
G
27. marec 2007, 17:52

Superfajn vini. ;-)

Te spremljam...

Aleš

4.
BS
8. september 2007, 01:07

Ejga, Vini, jaz sicer niti malo ne dvomim v tvoje znanje in erudicijo, ampak tale tvoja koda zgoraj je malo ...zašla - že kar v sintaksi: je malo čudno videti, če napišeš:
class tapata() {
... pravilno bi bilo:
class tapata {
... čudno da ti PHP ob izvajanju skripte tega ni povedal ... problem GC pa ni tako zapleten, kot bi se morda zdelo. Tudi v PHP 5, kot v vseh ostalih OO jezikih obstaja, poleg konstruktorja, tudi destruktor, ki je uporaben še za kaj drugega, kot, bi se na prvi pogled zdelo - samo za destrukcijo. Sicer pa, saj vse piše v Help datotekah, oz. referencah za PHP, tole recimo na:
http://www.php.net/manual/en/language.oop5.decon.php
GC sam po sebi ne bo naredil nič (bi bilo sranje, če bi), se strinjam, o sproščanju pomnilnika pa bi z uporabo funkcije unset seveda lahko ...filozofirali; sicer pa:
http://www.php.net/manual/en/function.unset.php

pa brez zamere...

5.
9. september 2007, 11:45

BS, res je, napaka v sintaksi, popravljeno. PHP mi ni javil syntax errorja preprosto zato, ker te kode seveda nisem poganjal, mišljena je bila bolj kot demonstracija problema in ne kot delujoča koda.

Drži tudi tvoja opazka, da bi lahko uporabil __destruct() metodo, sicer za prikaz problema in njega rešitve povsem nepomembno, pa vseeno, imaš prav, uporaba te metode bi bila bolj elegantna :)

6.
9. september 2007, 12:28

BS, še ena zadeva, si me zrajcal in sem šel zadeve ponovno preverjat. V verziji 5.2 gornjega problema očitno ni več, pojma nimam, v kateri verziji PHPja sem ga imel :)

Tale objava je pač postala obsolete, jebiga :)

7.
27. julij 2008, 13:26

Evo, spet naletel na ta isti problem z SimplePie RSS parserjem, ocitno zadeva vseeno bi obsolete, kot sem pomislil v zgornjem komentarju.

Opis buga je tukaj, zelo podoben nacin workarounda, kot sem ga predlagal zgoraj, pa tukaj.

Jebiga, __destruct() metoda se ne klice avtomatsko, ce PHP, navkljub uporabi unset(), misli, da tega objekta se ni potrebno destructati.