PD Stefan Bosse
Universität Bremen, FB Mathematik & Informatik
SS 2020
Version 2020-06-10 sbosse@uni-bremen.de |
Variablen werden mit dem Schlüsselwort local
definiert → Erzeugung eines Datencontainers!
Es gibt keine Typendeklaration in Lua! Kerntypen:
Tcore={number, boolean, table, string, function}
Alle Variablen sind polymorph und können alle Werttypen aufnehmen (auch dynamisch wechselnd zur Laufzeit).
Bei der Variabledefinition kann ein Ausdruckswert zugewiesen werden
local v = ε,..; v = ε;
Funktionen können mit einem Namen oder anonym definiert werden
Funktionen sind Werte 1. Ordnung → Funktionen können Variablen oder Funktionsargumenten zugewiesen werden
Eine Funktion kann einen Wert mit der return
Anweisung zurückgeben. Ohne explizite Wertrückgabe → undefined
Es wird nur Call-by-value Aufruf unterstützt - jedoch werden Objekte, Funktionen und Arrays als Referenz übergeben; Parameter pi sind an Funktionsblock gebunden
function name (p1,p2,..) statements ; return ε end ⇒ name(ε1,ε2,..)
Da in JavaScript Funktionen Werte erster Ordnung sind können
return
)
Es können daher anonyme Funktionen function (..) {..}
definiert werden die entweder einer Variablen als Wert oder als Funktionsargument übergeben werden.
local x = function (pi) ε(pi) end
array.forEach(function(elem,index) ε(pi) end
In Lua sind Objekte universelle Datenstrukturen (sowohl Datenstrukturen als auch Objekte) die mit Hashtabellen implementiert werden. Arrays werden in Lua ebenfalls als Hashtabelle implementiert!. D.h. Objekte == Datenstrukturen == Arrays == Hashtabellen.
Es gibt kein nutzerdefinierbares Typensystem in Lua.
Eine Datenstruktur kann jederzeit definiert und verändert werden (d.h. Attribute hinzugefügt werden)
local dataobject = {
a=ε,
b=ε, ..
f=function () { .. }
}
..
dataobject.c = ε
dataobject.attribute
dataobject["attribute"]
array[index]
array["attribute"]
Objekte zeichnen sich in der objektorientierten Programmierung durch Methoden aus mit der ein Zugriff auf die privaten Daten (Variablen) eines Objekts möglich wird.
In Lua kann auf Variablen eines Objekts (die Attribute) immer direkt zugegriffen werden.
Attribute können Funktionen sein - jedoch können die Funktionen nicht wie Methoden direkt auf die Daten des Objektes zugreifen.
Daher definiert man Methoden über Prototypenerweiterung in Lua.
Die Methoden können über die self
Variable direkt auf das zugehörige Objekt zugreifen (also auch auf die Variablen/Attribute)
Es gibt eine Konstruktionsfunktion für solche Objekte mit Prototypendefinition der Methoden
Objekte werden mit dem new
Operation durch die Konstruktionsfunktion erzeugt.
constructor=class()
function constructor:init (pi)
self.x=ε
..
end
function constructor:methodi (..) {
self.x=ε;
..
}
...
local obj = constructor:new(..);
Isolierte Programmprozesse ohne direkte Synchronisation und Dateaustausch zwischen den VMs
Synchronsiation nur über
Jeder Prozess führt eine VM aus
Es werden parallele Prozesse auf Threads abgebildet die bestenfalls 1:1 auf den CPUs / Cores ausgeführt werden, ansonsten durch einen Scheduler im Zeitmultiplexverfahren ausgeführt werden
Prozesse können synchronisiert auf geteilte Objekte (konkurrierend) zugreifen:
Prozessblockierung blockiert den gesamten Thread (somit auch alle Fibers)
Jeder parallele Prozess in einem Thread läuft in eigener VM Instanz
Daten können nicht zwischen VMs ausgetauscht werden (Automatsisches Speichermanagement und GC) → nur Austtausch über Serialisierung und Kopie von Daten!
Es werden pseudo-parallele Prozesse (Coroutinen) auf Fibers abgebildet die grundsätzlich nicht parallel aber durch einen Scheduler im Zeitmultiplexverfahren ausgeführt werden.
Diese Prozesse können synchronisiert auf geteilte Objekte (nicht konkurrierend) zugreifen:
Prozessblockierung blockiert nur einen Fiber
Alle parallelen Prozesse mit Fibers laufen in einer VM Instanz
Neben Thread, Fibers, und Prozessen kann auch die Parallelisierung von Ein- und Ausgabeoperation genutzt werden
Asynchrone IO wird sowohl in nodejs (JavaScript) als auch in Lua (lvm) mittels der libuv Bibliothek implementiert
Fibers (Coroutinen) werden in Lua direkt durch die Lua VM verarbeitet
Threads werden über die libuv einzelnen VM Instanzen zugeordnet
Fibers teilen sich eine VM Instanz und können direkt auf geteilte Datenobjekte zugreifen (gleicher Gültigkeistbereich der Daten für alle Fibers)
Threads können keine Daten teilen. Nur Datenkopien können geteilt werden:
Da Funktionen Werte erster Ordnung sind können auch Funktionen serialisiert werden
Die Csp.lua
Bibliothek implementiert das CCSP Multiprozessmodell mittels Multithreading von lvm
Die Bibliothek muss mittels require('Csp')
geladen werden
Der Seq Konstruktor erzeugt eine sequenzielle Komposition von Prozessen (über einer Array von Funktionen definiert). Da Lua inhärent sequenziell ausgeführt wird ist dies gleichbedeutend mit: f1();f2();..
Der Par Konstruktor erzeugt eine parallele Komposition von Prozessen (über einer Array von Funktionen definiert). Alle Subprozesse werden nach Erzeugung automatisch gestartet. Der aufrufende Prozess wird blockiert bis alle Subprozesse terminiert sind. Ein shared environment kann mit geteilten (IPC) Objekten übergeben werden.
Prozesse können sich Ressourcen teilen:
Geteilte Ressourcen müssen explizit erzeugt werden und als zweites Argument an die Prozesskonstruktoren übergeben werden.
Par({
function process1 ()
local x;
x=ch:read();
print(x);
end,
function process2 ()
local x=math.random();
ch:write(x);
print(x);
end
},{
ch=Channel(1)
})
In einem sequenziellen Prozess führt ein nicht behandelter Fehler zum Abbruch dieses Prozesses und wenn es der einzige ist zum Abbruch des Programms (Standard in der Programmierung). Aber:
Wenn ein Teilprozess eines parallelen Metaprozesses einen Fehler verursacht (also ein Ausnahmefehlersignal erzeugt) wird nur die Ausführung dieses Teilprozesses beendet.
Der Par Kosntruktor wartet auf Terminierung aller Teilprozesse. Mittels eines optionalen greedy Arguments kann der Metaprozess auch bei einem Fehler terminieren.
Par({
function ()
Alt({
function process1,cond ()
x=ch1:read();
print('ch1: hello '..x);
end,
function process2,cond ()
x=ch2:read();
print('ch2: hello '..x);
end,
..
})
end,
function ()
ch2:write('world')
end,
{
ch1=Channel(1), ch2=Channel(1), ..
})
ParArray(function (index)
..
end,
N,,
{shared}
wait?
)
channel = Channel:new(depth)
x = channel:read()
channel:write(ε)
Es stehen folgende Operationen zur Verfügung:
semaphore = Semaphore:new(init)
semaphore:up()
semaphore:down()
… weitere folgen …
Eine n-dimensionale Float Matrix die zwischen parallelen Prozessen geteilt werden kann.
Der Zugriff auf die Zellen der Matrix erfolgt mit den Operationen m:read(index1:number, index2:number, ..)
→ number
und m:write(val:number,index1:number, index2, ..)
. Es gibt die Möglichkeit die Float Matrix mittels der m:slice(range1:(number|number []|nil), range2, ..)
Operation in ein (nicht teilbares) natives Array zu wandeln. Der Platzhalter nil
wählt den gesamten Indexbereich der jeweiligen Dimension der Matrix aus.
Ein Byte Buffer (Elementtyp unsigned byte).
Der Zugriff auf die Zellen der Matrix erfolgt mit den Operationen m:read(index:number)
→ number
und m:write(val:number,index:number)
.
local m1 = Matrix({1000,1000},function () return math.random() end);
local m2 = Matrix({1000},function () return math.random() end);
Par({
function ()
for i=1,500 do
for j=1,500 do
m1:write(i,j,m1:read(i,j)*m2:read(j));
end end
end,
function ()
for i=501,1000 do
for j=501,1000 do
m1:write(i,j,m1:read(i,j)*m2:read(i));
end end
end
},{
m1:m1,
m2:m2
});