Párhuzamosság
Clipper:
A Clipper nyelv még monotaskos operációs rendszerre lett tervezve (DOS), nem támogatja a párhuzamosságot.
Ennek ellenére a kódblokk adattípus segítségével meg lehetett valósítani nem valós, de annak tűnő többszálúságot nem preemptív ütemezéssel.
Az alapgondolat
Általánosságban elmondható, hogy a legtöbb Clipperben írt program saját billentyű bekérési függvényt implementál,
hogy képernyőkímélőt és más szolgáltatásokat tudjon nyújtani. Ha jobban megvizsgáljuk, a program ebben a függvényben áll 99%-ban
(amikor nem valamilyen felhasználói interakcióra reagál). Ez lehetőséget ad rá, hogy ebben a függvényben futtassunk előre megadható kódblokkokat,
amik a program futása alatt attól függetlenül futhassanak, hogy éppen a program melyik részén járunk!
A megvalósítás
- készítsünk egy üres PRG állományt, a következő forráskódokat ebbe implementáljuk. Ennek az oka, hogy a szálkezelés egységbe zárt,
néhány interfész függvényen keresztül elérhető, csak általunk szabályozott módon biztonságosan működő, szolgáltatás legyen
- készítsünk egy fájl szintű statikus (fájl szinten látható) tömböt. Ebbe gyűjtjük a futtatandó szálak információit.
Ebben minden tömbelem egy altömb, ami egy szálat reprezentál (egy szálnak több tulajdonsága van)
static aThreadList
- készítsünk egy metódust, ami ebben a tömbbe helyez el új elemeket. A kapott paraméter alapján az aThreadList tömbbe egy
altömbelemet kell tenni, ami a létrehozott szálat reprezentálja és a szál különböző tulajdonságait tárolja. A kapott paramétereken
kívül tárolni kell a következő aktivizálódást idejét. Fontos, hogy a függvény adjon vissza egy egyedi azonosítót, amivel később
hivatkozni tudunk a létrehozott szálra. Ha nem lehet létrehozni már új szálat, adjon vissza -1-t
function NewThread(bThread, sName, dFirstRunDate, cFirstRunTime, nRepeatIntervall) -> nThreadId
- készítsünk egy metódust, ami törli a tömbből a megadott szálat reprezentáló elemet. Térjen vissza a törlés
megtörténtének sikerességével
function KillThread(nThreadId) -> lSuccess
- készítsünk egy függvényt, amivel le lehet futtatni azokat a szálakat, amiknek a legutolsó hívás óta elérkezett a futtatási időpillanata.
Miután lefuttattunk egy szálat, vizsgáljuk meg, hogy kell-e még futtatni (nRepeatInervall > 0) vagy nem. Ha nem kell, akkor töröljük a
listából, ha kell, akkor számoljuk ki az új futtatási idejét, írjuk vissza a reprezentáló tömbbe.
Ha minden érintett szál lefutott, rendezzük be a tömböt a következő futás időpontja szerint. Ez lehetővé teszi, hogy a tömbben az elejétől addig
kelljen csak menni legközelebb az aThreadList-ben, amíg olyan elemre lépünk, akinek futnia kell, az első még nem aktuális elemnél kiléphetünk.
Fontos: csak egyszer iteráljunk végig az a ThreadList tömbön egy futtatáson belül. Így garantálva lesz, hogy véget érjen
a szálak futtatása (ha a szálak maguk végetérnek)
function RunThreads() -> NIL
- A billentyűzetkezelőbe helyezzük el a RunThreads() függvényhívást. Amennyiben csak másodperc pontossággal akarunk időzíteni,
akkor megéri a billentyűzetkezelőbe lekezelni, hogy csak akkor menjen a RunThreads()-ra a vezérlés, ha változott a másodperc a
legutolsó futtatás óta
static aThreadList := {}
//Az aThreadList tömb pozíció konstansai
#define TLPOS_THREADID 1 //A szál azonosítója
#define TLPOS_CODEBLOCK 2 //A futtatandó kódblokk
#define TLPOS_NAME 3 //A szál neve
#define TLPOS_NEXTRUNDATE 4 //A következő lefutás dátuma
#define TLPOS_NEXTRUNTIME 5 //A következő lefutás időpontja
#define TLPOS_REPEATINTERVALL 6 //Az ismétlődési periódus ideje
//A NewThread() visszatérő hibakódjai
#define NTRET_THREADLIMITERR -1 //A szálszám túllépése hiba
function NewThread(bThread, sName, dFirstRunDate, cFirstRunTime, nRepeatIntervall)
local nThreadId
if len(aThreadList) <= 4095
//Szálazonosító képzés
if empty(aThreadList)
nThreadId := 1
else
nThreadId := aThreadList[len(aThreadList), TLPOS_THREADID]
endif
//Szál adatainak tárolása
aadd(aThreadList, {nThreadId, bThread, sName, dFirstRunDate, cFirstRunTime, nRepeatIntervall})
else
//Ha nem lehet létrehozni, -1-el visszatérni (hibakód)
nThreadId := NTRET_THREADLIMITERR
endif
return nThreadId
function KillThread(nThreadId)
local lRet, nPos := ascan(aThreadList, {|x| x[TLPOS_THREADID] = nThreadId})
if nPos > 0 //Ha létezik
adel(aThreadList, nPos)
asize(aThreadList, len(aThreadList) - 1)
lRet := .T.
else
lRet := .F.
endif
return lRet
function RunThreads()
local i, aCurrThread, dNextDay, cNextTime
for i := 1 to len(aThreadList)
aCurrThread := aThreadList[i]
if aCurrThread[TLPOS_NEXTRUNDATE] <= date() .and.;
aCurrThread[TLPOS_NEXTRUNTIME] <= time()
//Futtat s
eval(aCurrThread[TLPOS_CODEBLOCK])
//Ha még kell futtatni, megállapítani a következő időpillanatot
if aCurrThread[TLPOS_REPEATINTERVALL] > 0
//Meg llapˇtani az Łj id‹pontot
AddSec(aCurrThread[TLPOS_NEXTRUNDATE], aCurrThread[TLPOS_NEXTRUNTIME],;
aCurrThread[TLPOS_REPEATINTERVALL], @dNextDay, @cNextTime)
//Letárolni az új értékeket
aCurrThread[TLPOS_NEXTRUNDATE] := dNextDay
aCurrThread[TLPOS_NEXTRUNTIME] := cNextTime
else
//Hacsak egyszer kellett lefutnia, törölni
KillThread(aCurrThread[TLPOS_THREADID])
loop //mivel ki lett törölve, ugyanezen a pozíción levő szállal kell folytatni az aThreadList-ben
endif
else
exit
endif
next
//újrarendezni a tömböt
asort(aThreadList, , ,;
{|x, y| dtos(x[TLPOS_NEXTRUNDATE]) + x[TLPOS_NEXTRUNTIME] < dtos(y[TLPOS_NEXTRUNDATE]) + y[TLPOS_NEXTRUNTIME]})
return NIL
Megjegyzések
Az itt vázolt implementáció erősen épít rá, hogy a kódblokkokban megadott szálak rövid ideig tartó folyamatot végeznek,
pl.: óra kirajzolása, dátum váltás figyelése, stb. Amennyiben olyan funkcionalitást teszünk ilyen szálba, ami jelentősebb
ideig megfogja a vezérlést, akkor a felhasználó számára az adatbevitel lelassulhat.
Mivel az implementáció nem preemptív ütemezést valósít meg, ha egy szál végtelen ciklusba kerül, akkor a program további
futása sem lesz lehetséges, és kívülről véletlenszerű helyeken fog a programunk „lefagyni”. Ennek megelőzésére ciklusokat
csak óvatosan használjunk a szálakban!
FoxPro
A nyelv nem támogatja a párhuzamosságot.