Oxygene

Párhuzamosság



Alapvetően System.Thread osztály használata lehetővé teszi új szál létrehozását, de ebben a fejezetben tárgyalt nyelvi eszközök segítségével elkerülhető a Thread osztály explicit használata. Helyette a fordító átvállalja a szálak kezelését és szinkronizációját.

Párhuzamos ciklusok

Oxygene támogatja a párhuzamos keretrendszert (parallel framework). Így megfelelő kulcsszóval a ciklus törzsét párhuzamosan is végrehajthatjuk. Ilyet típusú ciklus csak akkor alkalmazható, ha PFX framework szerepel projektben a referenciák között. Használata esetén a ciklus elemei külön szálban/külön processzoron futnak és a ciklus addig tart, amíg az összes iterációja nem fejeződött be. Ha egyik iterációban kivétel képződik, akkor az egész ciklus leáll úgy, hogy leállítja az összes iterációt és az eredeti kivétel helyett egy új, egész ciklusra vonatkozó kivételt vált ki az eredeti szálban.

Párhuzamos ciklusoknál Exit használata nem megengedett. Break leállítja az aktuális szálat, de a már futó iterációkat nem állítja le. Continue pedig aktuális iterációt állítja le és következőhöz kezd. Mind a for és for each ciklusban is használható a párhuzamosság, azzal a kivétellel, hogy downto használata nem megengedett:

for parallel i: Integer := 0 to 10 do ... // működik for parallel i: Integer := 1 to 49 step 7 do ... // működik for parallel i: Integer := 10 downto 0 do ...// nem működik for each parallel elem in SomeCollection do … //működik

Utóbbi esetben SomeCollection osztálynak implementálnia kell az IEnumerable generikus interfészt.

Szál szinkronizáció

loking kulcsszó használata lehetőséget ad a szálak közötti szinkronizációra mutexek segítségével.
Például:

locking MyObject do begin ... end;

Egy objektum zárolása biztonságot ad abban az esetben, amikor objektum használata módosítás közben hibás működést eredményezhet.

Vigyázat, a zárolás gyakori és szükség nélküli használata teljesítménycsökkenést eredményezhet.

locked kulcsszót tulajdonságoknál, metódusoknál vagy eseményeknél használhatjuk. Ezzel fordítóra bízva a megfelelő szálszinkronizációs feladatokat a tulajdonságon/metóduson/eseményen belül.

Metódus esetén csak nem virtuális metódusokkal használható a locked kulcsszó.

Aszinkron végrehajtás

async kulcsszó használatával egy adott metódust aszinkronnak definiálhatjuk, ami azt jelenti, hogy meghívása után a vezérlés azonnal visszatér és a metódus külön szálban hajtódik végre. Ilyen metódusnak nem lehet visszatérési értéke és var vagy out típusú paramétere.

Egy új nyelvi elem, mely többmagos processzorokra írandó programok hatékonyságát növelni hívatott a „future”. Future egy erősen típusos változó, amely értéke vagy már ki van számítva, vagy még nincs, de biztos, hogy mire szükség lesz rá, ki lesz számítva. Például tekintsünk az alábbi példát, mely egy fában tarolt értékek összeget számolja ki:

method ThreeNode.Sum: Int32; begin var l: Int32 := Left.Sum; var r: Int32 := Right.Sum; result r+l; end;

Itt először bal oldali részfa összegét számoljuk ki, utána a bal oldalit. Végül összeadjuk a két értéket. Egy-egy részfa összegének kiszámítása sok időt vehet igénybe, de a bal oldali részfa összegére semmi szükség az utolsó sorig. Ezt következőképpen oldhatjuk meg future használatával:

method ThreeNode.Sum: Int32; begin var l: future Int32 := async Left.Sum; var r: Int32 := Right.Sum; result r+l; end;

Az „l” közvetlen kiszámítása helyett future Int32-ként deklaráljuk, ami azt jelenti, hogy „l” valóban nem tartalmazza a Left.Sum értékét, hanem megígéri, hogy valamikor az lesz az értéke. Virtuálisan a kód első sora nem telik időbe és a metódus továbblép „r” értékének kiszámításához.

„l” értékét leíró kifejezés is kapott az async kulcsszót, ami aszinkronná tette a kifejezést. Ez egy külön szál (vagy PFX Work Task) létrehozását fog eredményezni, mely kiszámítja „l” értékét aszinkron módon. Így amíg a főszálunk „r” értékén dolgozik egy másik szál (és remélhetőleg egy másik processzor vagy processzormag) el lesz foglalva „l” értékének kiszámításával.

A metódusunk utolsó sorában egyszerűen összeadjuk a két eredményt + operátort használva. Annak ellenére, hogy „l”-t future Int32-ként deklaráltunk Int32 minden tulajdonságát örökölte és sima Int32-ként használható.

Egy future típusú változónak három belső állapota lehet:

Abban a pillanatban, amikor szükségünk lett „l” értékére future az állapotától függően viselkedik. Ha az érték készen áll, akkor visszatér vele. Ha az érték folyamatban van, akkor a hívó szál blokkolva marad amíg „l” értéke nem lesz elérhető. Utolsó esetben viszont felesleges szálhasználat elkerülése érdekében a hívó szálban számítja ki az értéket (Azaz mintha nem lett volna a future kulcsszó a deklarációban).

Aszinkron future típus mellett lehetőségünk van szinkron future használatára. Aszinkron future-khez képest szinkron future-k végrehajtásához nem jön létre külön szál, hanem az érték hívó szálban kerül kiszámításra, de csak az első hozzáférés során.

Funkcionális szempontból mind a szinkron és aszinkron future azonos működést eredményeznek, ha kizárólag csak a program viselkedésével foglalkozunk. Egyetlen különbség, hogy a számításra külön szálban kerül sor.

Tipikus használati eset a késeltetett inicializáció. Például, ha egy olyan inicializálási idejét nézve „olcsó” osztály egy adattagja inicializálási idejét nézve „drága” osztály, akkor elképzelhető a következő megoldás:

type Cheap = public class private fCostly: future Costly := new Costly(); end;

A „drága” osztály inicializálása mégis elvesz sok időt, de nem az „olcsó” osztály inicializáláskor, hanem a „drága” osztály első használatakor.

Bizonyos esetben azt szeretnénk, hogy adott művelet végrehajtása befejeződjön a program bizonyos pontjáig, de addig a végrehajtása nem befolyásolja a főszál munkáját. Ebben az esetben használhatjuk az úgy nevezett érték nélküli future-ket. Például:

... var w: future := async begin ... // work in background thread done here ... end; ... // more work in main thread done here ... w(); // waits for the async block to finish

itt w(); nem valódi kifejezésblokk-hívás, hiszen a kifejezésblokk végrehajtása aszinkron módon elindultatott korábban. w(); csupán biztosítja, hogy az adott kifejezésblokk végrehajtása be lesz fejezve miután w() visszatér.