A Lua programozási nyelv

Helyesség

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.

Unscheduled threading (korutinok)

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:

co = coroutine.create(function() ... end)

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:

co = coroutine.wrap(function(a) local c = coroutine.yield(a+2) return c * 2 end)
Az első alkalommal, amikor a korutin aktiválódik, a meghívásnál átadott minden extra argumentum átadódik a korutin main függvényének. Pl, ha így hívjuk meg a korutint:
b = co (20)
a korutin függvényben "a" értéke 20 lesz. Amikor felfüggesztődik, minden a yieldnek átadott argumentum a hívónak adódíik. Így a példánkban a korutin eredménye 22 (a+2).
Amikor újra aktiválódik, minden extra argumentum átadódik a yieldnek. Így a példánkban
d = co(b+1)
hatására a lokális c változó értéke 23 lesz (b+1)
Végül, ha egy korutin terminál, minden érték amit a main függvénye visszaad, az utolsó meghívási ponthoz kerül. A mi esetünkben ez az eredmény 46 (c*2) adódik d-nek

Generátorok

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.

function preorder(node) if node then preorder(node.left) coroutine.yield(node.key) preorder(node.right) end end function preorder_iterator(tree) return coroutine.wrap(function() preorder(tree) end) end

A bináris fa iterátorra példaként tekintsük azt a feladatot, hogy két fát szeretnénk összefésülni.
A merge függvény két bináris fát kap paraméterként. Kezdetben létrehoz két iterátort a két fának (it1 és it2) és kigyűjti a legkisebb elemet (v1 és v2). A while ciklus kiírja a legkisebb értéket, majd újra meghívja a megfelelő iterátort, hogy meghatározza a következő elemet, amíg mindkét fából el nem fogynak.
function merge(t1, t2) local it1, it2 = preorder_iterator(t1), preorder_iterator(t2) local v1, v2 = it1(), it2() while v1 or v2 do if v1 ~= nil and (v2 == nil or v1 < v2) then print(v1); v1 = it1() else print(v2); v2 = it2() end end end

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.

Scheduled Threading

Luában a scheduled threading megvalósításához leggyakrabban a task library-t használják. Először is a

require("task")

direktíva segítségével importáljuk a 'task' library csomagot, ezután a

task.create ("scrip_name.lua", {parameter_list})

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

message, flags, err = task.receive (time)

és a

task.post ( returnto, message, flags)

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:

task.id () task.register (name) task.find (name)

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:

task.cancel (id) task.list() task.unregister ()

Az alábbi egyszerű program bemutatja az általános működést:

main.lua:

require("task") local child = task.create ("secunder.lua", {task.id()}) if child == -1 then io.stdout:write( "-> Can't expand task list.\n" ) elseif child == -2 then io.stdout:write( "-> Can't strdup file name.\n" ) elseif child == -3 then io.stdout:write( "-> Can't create message queue.\n" ) elseif child == -4 then io.stdout:write( "-> Can't create os thread.\n" ) elseif child == -11 then io.stdout:write( "-> The library seems corrupt.\n" ) else io.stdout:write( "-> Task ", child, " started.\n" ) end while os.clock() < 2 do -- sleep end task.post (child, "Luke, I'm your father!") while os.clock() < 4 do -- sleep end local buf, flags, err = task.receive( 0 ) if buf ~= nil then print (buf) else print ("no reply") end

secunder.lua:

require("task") function startReader (parent) -- local message = task.receive( -1 ) local message = nil while not message do message = task.receive( 0 ) end if message ~= nil then print (">> ".. message ) task.post(parent, "!!! Noooo !!!") end end if arg[1] ~= nil then startReader (arg[1]) end

Bővebb információ az alábbi oldalon található:

Lua Task Reference guide