A Lua nem tartalmaz valódi párhuzamos programok készítésére alkalmas eszközöket, lehetővé teszi viszont több végrehajtási szálon működő programok írását.
A Lua thread típusa nem keverendő össze az operációs rendszerek szál fogalmával. Egy Lua-beli thread akkor is párhuzamos működést szimulál, ha az adott rendszer nem támogatja a párhuzamos működést.
Luában úgynevezett korutinok segítségével érhetünk el több végrehajtási szálon való működést. A korutinokon végezhető műveletek az (újra)meghívás illetve a felfüggesztés, amikor a vezérlés visszaadódik a korutin meghívójának.
Ez a megközelítés is - mint minden más a Luában - az egyszerűség javát szolgálja, a bonyolultabb szálakkal ellentétben itt ezek a korutinok tényleg rutinszerűen viselkednek, a vezérlés mindig a hívóhoz adódik vissza, így könnyebb követni egy program futását.
Három alapvető művelet tartozik a korutinokhoz: create, resume, yield. Mint a legtöbb Lua programkönyvtár esetében, ezek a műveletek is egy globális táblában vannak elhelyezve (table coroutine).
A coroutine.create utasítás létrehoz egy új korutint és lefoglal számára egy saját stacket a
futtatásához. Paraméterként egy függvényt kap, ami a korutin törzsét tartalmazza, és egy korutinra
mutató referenciát ad vissza. Fontos, hogy a korutin létrehozása még nem indítja el azt!
A korutin kezdetben suspended, azaz felfüggesztett állapotba kerül és a continuation pointja (ahonnan folytatni kell)
a törzsének első utasítására állítódik.
Gyakran a coroutine.create paramétere egy név nélküli függvény:
Lua korutinokat értékül adhatunk változóknak, átadhatjuk őket paraméterként és vissza lehet adni őket eredményként. Explicit művelet nincsen korutinok törlésére, mint minden más esetben, erről is a szemétgyűjtő gondoskodik.
A coroutine.resume függvény aktiválja a korutint és első kötelező paramétereként egy referenciát kap az aktiválandó korutinra. Ekkor a korutin futni kezd az eltárolt continuation pointnál, amíg fel nem függesztődik újra vagy nem terminál. Akármelyik következik be, a vezérlés a korutin meghívási pontjához kerül vissza.
Korutin felfüggesztésére a coroutine.yield szolgál. Meghívásakor a korutin futási állapota elmentődik. A legközelebb, amikor a korutin aktiválódik, az elmentett állapotból folytatódik a futása.
Egy korutin terminál, ha a main függvénye return utasításhoz érkezik. Ebben az esetben halott állapotba kerül és nem aktiválható újra. Ugyancsak terminál egy korutin, ha hiba lép fel a futása során. Normális teminálás esetén a coroutine.resume truet ad vissza, illetve minden értéket amit a korutin main függvénye visszaad. Hiba esetén falset és egy hibaüzenetet.
A coroutine.createhez hasonlóan a courutine.wrap is egy új korutint hoz létre, azonban egy korutin referencia helyett, egy függvényt ad vissza, ami folytatja a korutint. Ennek a függvénynek átadott minden paraméter továbbadódik extra paraméterként a resumenak. Ugyanígy a függvény visszaad minden értéket, amit a resume visszaad, kivéve a státusz kódot. A coroutine.resumeal ellentétben, ez a függvény nem kapja el a fellépő hibákat, minden a korutinban fellépő hibát továbbad a korutin hívójának.
A Lua nagyon kényelmes megoldást biztosít egy korutin és a hívója közti adatcseréhez. Nézzük az alábbi kódot:
A generátorok értékek sorozatát hozzák létre, minden meghíváskor egy új értéket adnak át a hívójuknak. Tipikus felhasználási területük az iterátorok, amelyek segítségével egy adatszerkezetet járhatunk be anélkül, hogy ismernénk annak megvalósítását. Luában a korutinok segítségével kényelmesen és szépen valósíthatunk meg ilyen iterátorokat, kihasználva azt, hogy a korutinok állapota elmentődik, amikor leállnak, illetve hogy értékeket átadhatnak a hívójuknak.
Ennek bemutatásához nézzünk egy klasszikus példát: egy bináris fa preorder bejárását. A fa csúcsait
egy tábla segítségével tároljuk, melynek 3 mezője van: key, left, right. A key tárolja a csúcs értékét (integer)
, a left és right mezők pedig referenciákat a csúcs gyermekeihez.
A tree_iterator függvény paraméterként a bináris fa gyökércsúcsát kapja és egy iterátort ad vissza,
ami a fa csúcsaiban található értékeket szolgáltatja. A fa bejárását egy rekurzív függvény (preorder)
végzi, ami a sorban következő értéket az iterátor hívójának adja át közvetlenül. A bejárás végét
a nil érték jelzi, amit az iterátor main függvénye ad vissza, amikor terminál.
Mint láttuk, a szálakkal ellentétben a korutinoknak explicit kérniük kell a felfüggesztésüket, ahhoz hogy egy
másik korutinra kerülhessen a vezérlés. Ennek köszönhetően egy egyszerű multitaszkos alkalmazás az alábbi módon könnyen megvalósítható:
A konkurrens taszkokat egy-egy korutin fogja jelenteni. Amikor egy új taszk létrejön, betesszük egy listába, amely az élő taszkokat
tartalmazza. Egy ciklus iteráljon egyfolytában ezen a listán, és folytassa az élő taszkokat, míg a már befejeződötteket törölje a listából.
Ezt az állapotot (hogy a taszk befejeződött) a korutin main függvénye jelezheti egy előre definiált visszatérési értékkel a dispatcher ciklusnak.
Természetesen bonyolultabb feladatokhoz (pl. operációs rendszerek, valós idejű rendszerek) ez a felfogás nem megfelelő, mindenképp preemptív
taszk ütemezésre van szükség, mellyel a Lua nem rendelkezik, de nem is ilyen feladatok megoldására tervezték.
Luában a scheduled threading megvalósításához leggyakrabban a task library-t használják. Először is a
direktíva segítségével importáljuk a 'task' library csomagot, ezután a
segítségével elindíthatunk más Lua script-teket. Ez leginkább az 'exec'-elésre hasonlít. Ekkor valójában egy teljesen különálló Lua script fut és a main és az exec-elt script között string-stream-en alapuló kommunikáció lehetséges. Ehhez a
és a
metódusokat használjuk. A paraméterként a maximális várakozási időt adhatjuk meg, így '0' esetén non-blocking receive-ről beszélhetünk míg a '-1' mint extremális érték megadása esetén, az eltelt időtől függetlenül egészen addig várakozunk, amíg nem érkezik üzenet. A metódusnak egy triple a visszatérési értéke, melyek rendre ebbol a hárombol áll: message, flags, rc viszont a szokásos Lua értékadásnak megfelelően, megtehetjük, hogy csak a message-nek adunk értéket. A flags változóban van lehetőség például a feladó task id-jának átadására, több task esetén. Ez ezen kívül nevesítéssel oldható meg. Ezekhez az alábbi függvények használhatóak:
Megj.: lehetőség van futó task leállítására, listázására, illetve unregistrálására is, az alábbi függvényekkel:
Az alábbi egyszerű program bemutatja az általános működést:
main.lua:
secunder.lua:
Bővebb információ az alábbi oldalon található:
Lua Task Reference guide