A processz és a processzek közti kommunikáció alapvető fontosságú fogalmak az Erlangban.
A processz egy önálló számítási egység, amely más processzekkel párhuzamosan létezik a rendszerben. Processz létrehozása a spawn/3 paranccsal történik:
Az új processz az alábbi függvényhívást kezdi el végrehajtani:
ahol Arg1,Arg2,...,ArgN az "Argumentum_lista" lista elemei.
(Az "Argumentum_lista" lista üres is lehet).
Ahhoz, hogy a spawn függvény sikeresen lefusson, biztosítanunk kell a felhasználandó
modul láthatóságát. Például Eshell használata esetén előbb a c(Modul) parancsot
kell kiadnunk, hogy a shell lefordítsa az adott modult.
Gondoskodnunk kell továbbá arról, hogy a függvényhívás a függvény aritásának megfelelően történjen.
Tehát ügyelnünk kell arra, hogy az argumentumok listájában megadott elemeknek a száma
igazodjon a hívandó függvény argumentumainak a számához.
A függvény visszatérési értéke az újonnan kreált processz azonosítója, mely szükséges a processzel való mindenféle kommunikáció során. A spawn függvényhívás azonnal visszatér, amint a processz létrejött, nem várja meg a függvény kiértékelését. A processz automatikusan terminál, amint a spawn paraméterben átadott függvény kiértékelése befejeződik. A függvény visszatérési értéke nyilván elveszik.
Az Erlangban a processzek közti kommunikáció egyetlen formája az üzenetküldés. Ehhez a ’!’ primitív biztosított:
Ez a kifejezés az Expr2 értékét küldi el az Expr1 által azonosított folyamat számára. Az Expr2 értéke egyben a Expr1 ! Expr2 kifejezés visszatérési értéke is lesz. Expr1 érétékének egy processz azonosítónak, egy regisztrált névnek (atomnak) vagy egy {Name,Node} párnak kell lennie. Itt a Name egy atom, Node pedig egy csúcs, amely szintén egy atom. Az üzenetküldés aszinkron módon történik.
A receive primitív szolgál üzenetfogadásra. Szintaxisa:
Az üzeneteket a fogadó a küldés sorrendjében kapja meg. Minden processznek vagy egy üzenetfogadó puffere (mailbox), az üzenetek itt tárolódnak feldolgozásukig. A fentiekben Pattern1,...,PatternN minták, melyekhez a mailboxbeli üzenetek kerülnek illesztésre (az illesztés a beérkezés sorrendjében történő szekvenciális keresés). Ha van illeszkedő üzenet és a megfelelő őrfeltétel teljesül, az üzenet kiválasztódik, kikerül a mailboxból, és a megfelelő BodyI kiértékelődik. A receive az utoljára kiértékelt BodyJ–beli kifejezés értékét veszi fel.
A receive szerkezet egy blokkoló szerkezet. Ha a receive szerkezetre kerül a vezérlés, akkor a receive mindaddig blokkol (meghatározhatatlan ideig), amíg egy olyan üzenet érkezik a folyamat számára, amely egyezik a receive szerkezetben felsorolt minták egyikével és egyben az adott mintához tartozó őrfeltételt is kielégíti.
Ez a példa az Erlang honlapján található "Concurrent Programming" aloldaláról származik (link). Tegyük fel, hogy adott két folyamat, ezek legyenek A és B . Az A folyamat futása során az alábbi utasítás hajtódik végre:
Itt a self/0 függvény az A folyamat processz azonosítójával fog visszatérni
(bővebben erről a "Processz azonosító küldése üzenetként" részben olvashatunk). Így az elküldött üzenet
a {A,{digits,[1,2,6]}} formát fogja ölteni.
Tegyük fel, hogy a B folyamat egy olyan függvényt hajt végre, amelyben az alábbi kódrészlet
található:
Itt egy szelektív üzenetfogadás történik (bővebben lásd a "Szelektív üzenetfogadás" részt). A receive primitívben csak az A folyamattól érkező, {digits,D} formájú üzenetek feldolgozása történik meg.
Amikor a B folyamat megkapja az A folyamat által küldött üzenetet, akkor az illesztésre kerül a receive primitívben található egyetlen mintához. Mivel az illesztés sikeres, ezért a B folyamat keretein belül végrehajtódik az analyse(D) függvény, amely a D-ben fogadott számjegyek feldolgozására szolgál.
Tegyük fel, hogy van három folyamatunk: A,B és C, továbbá, hogy A az msg1 üzenetet küldi C-nek és B pedig a msg2 üzenetet. C-ben az üzenetfeldolgozó rész a következő módon néz ki:
Ekkor C a msg1 üzenetet fogja először feldolgozni, aztán a msg2 üzenetet, függetlenül az üzenetek elküldésének a sorrendjétől.
Tegyük fel, hogy az egyik folyamatunkban az alábbi üzenetfeldolgozási rész található:
Ekkor bármely beérkező üzenetre a Msg mintához tartozó törzs fog végrehajtódni (Fontos, hogy amíg msg egy atom, addig Msg egy változó. Amíg a Szelektív üzenetfogadás részben jól meghatározott atomok fogadására voltunk felkészülve addig itt a változó segítségével tetszőleges atomot feldolgozhatunk egyetlen receive ágban). Ekkor fontos, hogy milyen sorrendben érkeztek az üzenetek a folyamat pufferébe. Ezt a technikát használhatjuk például üzenetek "elnyelésére" is, mint ahogy arról lentebb az Időtúllépés rész példái között olvashatunk.
Tetszőleges folyamat végrehajtása közben az adott folyamat azonosítóját megkaphatjuk a self/0 függvény segítségével és így lehetőségünk nyílik azt más folyamatok számára elküldeni. Az üzenetek fogadói így tudni fogják, hogy mely folyamattól kaptak üzenetet.
Tegyük fel, hogy van három folyamatunk, ezek legyenek A,B és C. Az alábbi példa azt fogja szemléltetni, hogy milyen módon viselkedhet egy folyamat üzenet továbbitóként. Az A folyamat az alábbi üzenetküldést intézi:
Tehát az A folyamat a msg1 üzenetet és a saját folyamt-azonosítóját küldi el a B folyamat számára. A B folyamat üzenetfeldolgozó része az alábbi módon néz ki:
Így a B folyamat az A folyamattól kapott üzenetet rögtön továbbítja a C folyamat számára, az A folyamat folyamat-azonosítójával egyetemben. Végül a C folyamat a következőképpen dolgozza fel az üzenetet:
Az alábbiakban egy egyszerű (ugynakkor szemléletes) példaprogramot láthatunk, amely a folyamatok létrehozását, és a folyamatok közötti kommunikációt szemlélteti. Ez a példa az Erlang honlapján található "Concurrent Programming" aloldaláról származik (link).
A go/0 függvényt végrehajtó folyamat a loop/0 függvény külön folyamatként való elindítását végzi el első lépéként, majd egy üzenetküldéssel folytatja az imént elindított folyamat számára. Az üzenetben elküldi a saját folyamat-azonosítóját, és a hello atomot. Mivel a receive szerkezet egy blokkoló szerkezet, ezért ezen a ponton a go/0 függvényt végrehajtó folyamat blokkolt állapotba kerül.
A loop/0 függvény üzenetfeldolgozási része két részre osztható: az első részben {From, Msg} formájúak üzenetek feldolgozása játszódik le. Ha ilyen üzenetet kap a folyamat, amely a loop/0 függvény végrehajtását végzi, akkor a Msg üzenetet visszaküldi a saját folyamat-azonosítójával egyetemben a feladónak. A további üzenetek fogadásáról a rekurzív függvényhívás gondoskodik. A második részben a stop atom fogadása történik meg. Ha ilyen üzenetet kap a folyamat, amely a loop/0 függvény végrehajtását végzi, akkor a szóban forgó folyamat befejezi a működését.
Miután a loop/0 függvényt végrehajtó folyamat visszaküldte a go/0 függvényt végrehajtó folyamatnak a tőle kapott üzenetet, az utóbbi a kimenetre írja a "P1 hello\n" stringet, majd egy stop atomot küld a loop/0 függvényt végrehajtó folyamatnak, amely ennek hatására befejezi működését. Ezután a go/0 függvényt végrehajtó folyamatnak is végetér a működése.
Az alábbi példaprogram a pi matematikai konstans értékét közelíti a getPI/2 függvény segítségével.
A használt módszer menete alapvetően a következő: választunk egy N természetes számot, és generálunk N
darab (x,y) párt, ahol x és y a (0,1) intervallumból veszik fel az értékeiket egyenletesen.
Legyen n azon (x,y) párok darabszáma, amelyeknek a távolsága az origótól kisebb vagy egyenlő, mint 1.
Ekkor pi közelítőleg 4*n/N.
A példaprogram ennek a módszernek egy párhuzamosított változatát alkalmazza. A getPI függvény első argumentumában (NProc) kell megadnunk, hogy hány folyamattal kívánjuk a számítást elvégezni. Második argumentumaként (NPoints) azt kell megadnunk, hogy hány párt szeretnénk generáltatni az egyes folyamatokkal.
A generate/1 függvény az NPoints argumentum alapján a következőket teszi: ha
NPoints egyenlő nullával, akkor 0 értékkel tér vissza, különben végrehajtja egy
"véletlen" pontnak a generálásást, és vizsgálatát.
A véletlen pont generálásának a menete a következő: X és Y értéke
a random modulban található uniform függvény segítségével kapnak értéket.
Ennek a függvénynek egy, a 0.0 és 1.0 intervallumból egyenletes eloszlással származó
lebegőpontos szám a visszatérési értéke.
A D változó értéke az X és Y változó által meghatározott pont
origótól mért távolsága lesz. Ha D kisebb vagy egyenlő mint 1.0, akkor a generate/1
függvény visszatérési értéke 1 és a generate/1 függvény rekurzív hívásának a visszatérési értékének
az összege lesz, ahol a rekurzív hívás argumentuma NPoints - 1. Ha D nagyobb mint 1.0, akkor
a függvény visszatérési értéke a generate/1 függvény rekurzív hívásának visszatérési értéke lesz,
az előbbi argumentummal.
Így a generate/1 függvény egy véletlen pont generálását és vizsgálatát
NPoints alkalommal fogja végrehajtani.
Az ant/2 függvény először inicializálja a random modul függvényeit a jelenlegi idővel, amelyet a now/0 függvény meghívásával kap meg. Ezután meghívja a generate/1 függvényt, melynek eredményét a Points változóban tárolja el. Az ant/2 függvényt végrehajtó folyamat végül egy üzenetküldést intéz az ant/2 függvény argumentumaként kapott Pid processz azonosítóval rendelkező folyamat számára. Ebben az üzenetben az ant/2 függvényt végrehajtó folyamat elküldi a saját folyamat azonosítóját és a Points változó értékét.
A master/2 függvény hasonlóan a generate/1 függvényhez az NProc argumentuma alapján
a következőt teszi: ha NProc egyenlő nullával, akkor a visszatérési értéke nulla lesz, különben
végrehajtja egy ant/2 függvényt végrehajtó folyamatnak az elindítását.
Ha NProc nem nulla, akkor a spawn/3 függvény meghívásával létrejön egy folyamat,
amely az ant/2 függvényt hajtja végre a self() és NPoints argumentumokkal.
Így az újonnan létrehozott folyamat a master/2 függvény számára fogja elküldeni az általa
végrehajtott generate/1 függvény eredményét. Ennek az új folyamatnak a folyamat azonosítóját
a függvény a Pid változóban tárolja el.
A Tmp változó értéke a master/2 függvény visszatérési értéke lesz, ahol a master/2
függvény argumentumai NProc - 1 és NPoints lesznek.
Végül a master/2 függvényt végrehajtó folyamat belép egy receive vezérlési szerekezetbe,
ahol is blokkolt állapotba kerül, és egészen addig blokkolt marad, amíg a legutóbb elindított Pid
folyamat azonosítóval rendelkező folyamattól nem kap egy megfelelő {Pid,Generated} üzenetet.
Ekkor a master/2 függvény visszatérési értéke Tmp értékének és Generated
értékének az összege lesz.
Így a master/2 függvényt végrehajtó folyamat visszatérési értéke az összes általa elindított,
ant/2 függvényt végrehajtó folyamatok visszatérési értékeinek az összege lesz.
A getPI/2 függvény feladata az, hogy meghívja a megfelelő argumentumokkal a master/2 függvényt, majd a függvény visszatérési értékéből kiszámítsa a pi matematikai konstans közelítését, végül a kapott eredményt formázottan a kimenetre írja.
Az alkalmazások implementálása során lehetnek olyan esetek, mikor sokáig tartó taskokat (feladatokat megvalósító programokat/ programrészleteket) kell N-szer lefuttatni. Például: állítsunk össze és küldjünk ki egy emailt 100-szor. Ha feltesszük, hogy a taskok egymástól függetlenek és párhuzamosíthatóak, akkor az alkalmazásunk futási ideje nagyban csökkenthető, ha a taskok párhuzamosan vannak végrehajtva.
A fenti problémára egy általános "demo" alkalmazás megtalálható és letölthető a példaprogramok között.
Egy folyamat címzésére nem csak a processz azonosítóját használhatjuk fel, hanem egy esetlegesen hozzá rendelt atomot is. Erre a feladatra az alábbi register/2 beépített függvényt használhatjuk fel:
Ez a függvény a Name argumentumban megadott atomot rendeli hozzá névként a Pid processz azonosítóval rendelkező folyamathoz. Ha egy névvel ellátott folyamat terminál, akkor a neve automatikusan kikerül a regisztrált nevek közül.
Két további beépített függvény is rendelkezésünkre áll:
Az alábbi processz regisztrálási példa az Erlang honlapján található "Concurrent Programming" aloldaláról származik (link).
A start függvény első lépésként egy server függvényt indít el (a num_anal csomagból) különálló folyamatként. Második lépésként beregisztrálja ezt a frissen indított processzt az analyser atommal.
Az analyse függvényt végrehajtó folyamat első lépésben a regisztrált analyser folyamathoz
küldi el a saját processz azonosítójával az analyse atomot és a Seq változót, amelynek
értékét argumentumként kapja meg.
Ezután a függvényt végrehajtó folyamat blokkolt állapotba kerül a receive primitív miatt, egészen addig, amíg
az {analysis_result,R} mintára illeszkedő üzenetet nem kap. Ha ez megtörténik, akkor a visszatérési érték
az R értéke lesz.
A receive primitív kibővíthető egy Timeout ággal:
A TimeoutExpr kifejezésnek egy integer-ré kell kiértékelődnie (vagy egy speciális atommá, erről lentebb olvashatunk), ez az érték lesz a várakozás időtartama milliszekundumokban mérve. Ha ezen idő letelte alatt egy üzenet sem választódik ki, időtúllépés következik be, és az BodyT értékelődik ki. Ebben az esetben a BodyT kifejezés visszatérési értéke lesz a receive...after kifejezés visszatérési értéke.
Két speciális eset fordulhat elő a TimeoutExpr kifejezés értékére vonatkozóan:
Lehetőségünk van a receive primitívet a következő módon használni:
Ekkor egyetlen üzenet sem kerül feldolgozásra, csak a meghatározott (TimeoutExpr kifejezés értékének megfelelő) idő után a BodyT kerül végrehajtársa. Ezen konstrukció segítségével készíthetünk például időzítőket (lásd: lentebbi példák).
Az alábbi példák az Erlang honlapján található "Concurrent Programming" aloldaláról származnak (link).
Az "alvást" megvalósító kódrészlet a következő:
Ebben a példában a sleep/1 függvény receive primitívében nincs üzenetfeldolgozást végző kódrészlet, így az ezt a függvényt végrehajtó folyamat összes ("alvás" alatt kapott) üzenete az üzenetfogadó pufferben marad. A folyamat a beérkező üzenetek figyelmen kívül hagyását T ideig végzi.
Az alábbi függvény egy adott folyamat határozatlan ideig történő felfüggesztését valósítja meg:
Az alábbi kódrészlet a timer modul részét képezi, és azért felelős, hogy egy folyamat saját maga számára állítson be időzített "figyelmeztetést":
A set/3 függvény a sleep/1 függvényhez hasonló szerkezetű, azonban T időnyi várakozás után a függvényt végrehajtó folyamat a Pid processz azonosítóval rendelkező folyamat számára küldi el az argumentumként kapott Alarm értékét.
A set_alarm/2 függvény egy olyan folyamatot indít el, amely a set/3 függvényt fogja végrehajtani a self(), T és What paraméterekkel. Az így elindított foylamat tehát a set_alarm/2 függvényt végrehajtó folyamat számára fogja T idő múlva fogja elküldeni a What értékét, ezzel valósítva meg az időzített "figyelmeztetést".
A végrehajtó folyamat üzenetfogadó pufferének kiürítését az alábbi programrészlettel lehet megtenni:
A flush/0 függvény receive ágában a beérkező üzenetek az Any változóval kerülnek mintaillesztésre, amely minden esetben sikeres lesz (mivel Any egy változó). Sikeres mintaillesztés esetén az illesztett üzenet kikerül a folyamat üzenetfogadó pufferéből, így jelen esetben a flush/0 függvény rekurzív hívásával az üzenet elvész. Miután a végrehajtó folyamat üzenetfogadó pufferéből elfogytak az üzenetek, az after ág gondoskodik róla, hogy a vezértlés kikerüljön az amúgy blokkoló receive primitívből.
Egy elosztott erlang rendszer tartalmaz több futásidejű rendszert, amelyek, ahogy a nevükben is benne van, futás időben kommunikálnak egymással. Minden ilyen rendszert node-nak nevezünk. A különböző node-ok folyamatai közötti üzenetek, a linkek és monitorok transzparensek, ha a folyamat azonosítót (pid) használják. A folyamatok regisztrált nevei azonban lokálisak minden node-on.
A node (csomópont) egy végrehajtható futásidejű erlang rendszer, amely kapott egy nevet a parancssori indítás során. A névadás történthez a –name és a –sname kapcsolóval. A kettő közötti különbség, hogy az előbbi egy hosszú nevet, míg utóbbi egy rövid nevet definiál. A formátum a node neve egy atom name@host, ahol a név a felhasználó által megadott és a szerver a teljes host neve, ha hosszú neveket használnak, vagy az első része a host nevének, ha rövid neveket használnak. A node() visszaadja a csomópont nevét.
Fontos, hogy egy hosszú névvel ellátott node nem tud kommunikálni egy rövid nevű node-al.
Elosztott erlangban a node-ok lazán kapcsolódnak. Kapcsolatok alapértelmezés szerint tranzitívak. Ha az A node csatlakozik a B node-hoz, és B kapcsolódik a C node-hoz, akkor az A node is próbál csatlakozni a C node-hoz. Ez a funkció kikapcsolható, a parancssor segítségével a connect_all kapcsoló hamisra állításával. Ha egy node leáll, minden kapcsolata törlődik. A disconnect_node(Node) erlang hívás arra fogja kényszeríteni a node-ot, hogy szétkapcsoljon. A nodes() függvény visszatérési értéke pedig egy lista , ami tartalmazza a node-hoz jelenleg kapcsolódó más node-okat.
Elosztott erlangban néha hasznos lehet, hogy úgy csatlakozzunk egy node-hoz, hogy közben nem létesítünk kapcsolatot az összes többi node-al. Erre a célra használható a rejtett(hidden) node. Egy ilyen node-ot parancssorból a –hidden kapcsolóval indíthatunk. A rejtett node-ok és más node-ok közötti kapcsolatok nem tranzitívak, ezeket expliciten kell beállítani. Ezen kívül a nodes() függvény által visszaadott listában a rejtett node-ok nem szerepelnek. Ha szükségünk van ezen node-ok listájára is, akkor használható ugyanezen függvény 1 paraméteres változata, amelynek meg kell adni, hogy rejtett(hidden) vagy kapcsolódott(connected) node-ok listájára van szükség.
Egy C node C nyelven írt program, ami egyfajta rejtett node az elosztott Erlangban. Erre a célra tartalmaz az Erl_Interface könyvtár függvényeket. További információk olvashatók a C node-okról a dokumentáció Erl_Interface and Interoperability Tutorial című részében.
Az Erlang filozófia szerint, igyekezzünk minden probléma megoldását abban a programozási nyelvben implementálni, amiben az a leghatékonyabb végrehajtást fogja eredményezni. Így az Erlang fejlesztői nagy hangsúlyt fektetnek a nyelv fejlesztése során arra, hogy a nyelv megőrizze nyíltságát, portolhatóságát.
Az Erlang R13B03-as verziója előtt, az Erlang node-ok nem Erlang node-okkal való összekapcsolásának a leggyakoribb módja a port driver-ek használata volt. Az Erlang R13B03-as verziójában bevezetett NIF-ek a C kódok hívásának sokkal egyszerűbb és hatékonyabb megoldását tették lehetővé. Fontosságát mutatja, hogy több régebbi alkalmazást is újraimplementáltak NIF-ek segítségével.
NIF (Native Implemented Function) egy függvény, melyet Erlang helyett hatékonysági okokból C nyelven implementáltak a fejlesztők. A NIF-ek használata az Erlang programozó szempontjából észrevehetetlen, transzparens: a NIF-es függvényeket ugyanúgy Erlang modulokba kell szervezni és ugyanúgy kell hívni, mint a natív Erlang függvényeket. A NIF-es függvények funkcionalitásának implementálása C / C++ nyelven történik, mely forrásokat felhasználva dinamikus megosztott könyvtárakat (shared library) kell linkelni belőlük (dll Windows-on, so Unix-on) ahhoz, hogy az Erlang node-unkhoz tudjuk kapcsolni őket.
Erlang-ból C kódot jelenleg a NIF segítségével lehet a lehető leggyorsabb hívni, amit többek között annak a technológiai megoldásnak köszönhetünk, hogy a NIF-es könyvtárak dinamikusan az emulátor folyamathoz vannak linkelve. A gyorsaságnak azonban ára is van: a NIF-ek használata a lehető legkevésbé biztonságos- egy NIF-es implementáción belül bekövetkező futás idejű hiba a teljes emulátort abnormális terminálásra készteti.
Egy nagyon egyszerű, demó jellegű NIF-es könyvtár megírásán keresztül fogom bemutatni a technológia alkalmazásához szükséges legfontosabb tudnivalókat.
A feladatunk legyen az, hogy implementáljunk egy olyan modult, ami lehetőséget biztosít két négyzetszám differenciájának kiszámításához. A számítást implementáljuk úgy Erlang modulunkban, hogyha mindkét szám egész, akkor egy „hatékonyabb” C implementáció végezze a számítást, minden további esetben pedig Erlang modulunk. Az első esetet, mikor két egész szám a paraméter, implementáljuk úgy, hogy a tetszőleges egész számok összeszorzását, illetve összeadását C nyelven implementált NIF -es függvényekkel fogjuk elvégeztetni.
A feladat lentiekben bemutatott megoldása megtalálható és letölthető a példaprogramok között.
Egy NIF-es könyvtár használatához elengedhetetlenül szükséges egy Erlang modul implementálása is, melynek példánkban nif_demo lesz az azonosítója.
A NIF-es könyvtárakat az Erlang programozónak abban az Erlang modulban amiben használni akarja azokat explicit be kell töltenie. A betöltés elvégzésére egy „best practice” technika az, hogy kihasználjuk az Erlang-os „on_load” direktíva lehetőségét. A direktívát definiáló attribútum form-ban meg lehet adni egy a modulra nézve akár lokális, 0 aritású függvényt.
Példánkban:
Az itt megadott függvény automatikusan meghívásra kerül, mikor a virtuális gép Code server folyamata a függvényt definiáló modult betölti. Az on_load direktívában megjelölt függvényben az erlang:load_nif/2 függvényt szükséges meghívni, átadva a betöltendő NIF-es könyvtár elérési útvonalát és a könyvtár inicializálásához szükséges értékeket paraméterül.
Példánkban:
A betöltés sikerességének kimenetelétől függhet akár a teljes Erlang-os alkalmazásunk futásának eredménye, így sikertelenség esetén célszerű a NIF-et használó modult kidobatni a virtuális gép Code server-ével. Ha az on_load direktívát használva kíséreltük meg a NIF-es könyvtár betöltését, akkor sikertelenség esetén nincs más dolgunk, mint bármilyen más érvényes Erlang termmel visszatérni, mint az ok atom. Ha nem az ok atommal térünk vissza az on_load direktívában megjelölt függvényben, akkor a virtuális gép Code server-e a modul betöltését megszakítja és eltávolítja a teljes modul BEAM assembly -ét.
Ha a betöltés sikeres volt, akkor a NIF-es könyvtár függvényeinek linkelése fog következni. Mivel a NIF-ek hívása az Erlang programozó szempontjából transzparens, így a linkelés megvalósítása úgy történik, hogy a NIF-es könyvtár egy függvényét a virtuális gép a könyvtárt betöltő Erlang modulban definiált, megegyező nevű és szignatúrájú Erlang függvény definíciójához (stub) fogja linkelni. Az ilyenfajta függvénydefiníciókat NIF-es függvénycsonkoknak is szoktuk nevezni.
Példánkban az összeadó függvény csonkja (stub-ja):
Ez azt vonja maga után, hogy a NIF-es könyvtár sikeres betöltése után, egy NIF-es függvény hívása esetén a vezérlés nem az Erlang-os csonkon folytatódik, hanem annak NIF-es megfelelőjén. Fontos, hogy a hívás szinkron végrehajtású, tehát a NIF-et hívó Erlang folyamat addig blokkolásra kerül, míg a NIF-es függvény vissza nem tér, visszaadva ezzel a vezérlést.
Célszerű megjegyezni, hogy bár a NIF-es függvénycsonkokat nem kötelező exportálni a modulból a helyes működéshez, de javasolt, mert előfordulhat, hogy az Erlang fordító kioptimalizálja a lokális, nem használt NIF-es függvénycsonkokat, amivel így a NIF-es könyvtár betöltése sikertelen lesz futási időben.
Nagyon fontos észben tartani azt, hogy a NIF-ek használata során kellő körültekintéssel kell eljárnunk, ugyanis:
Míg Erlang oldalon a NIF-es könyvtárat használó Erlang modulunk alig különbözött az eddig megszokott Erlang moduloktól, addig a NIF-es függvények C oldali implementációjakor több tényezőre kell figyelnünk.
Egy modul által használt NIF-eket egy megosztott könyvtárba kell fordítani és linkelni. Egy NIF-es függvény implementációja egy C-beni függvény implementációjának feleltethető meg. Példánkban a forrásfájl neve legyen nif_demo.cpp, a linkelt állomány neve pedig nif_demo.so.
Minden NIF-es implementációt tartalmazó C forrásfájlban include-olni kell az erl_nif.h fejlécfájlt, mely többek között tartalmazza az Erlang – C, C- Erlang típusok közötti konverziókat megvalósító függvények definícióját, és további nélkülözhetetlen makrókat, szolgáltatásokat nyújt. A fejlécfájlt az Erlang gyökérkönyvtárának usr/include könyvtárában (/usr/local/lib/erlang/usr/include) találjuk meg Unix típusú rendszereken, ha az alapértelmezett paraméterekkel, forrásból fordítva telepítettük fel az Erlangot. Mivel nem végrehajtható programot fogunk fordítani a C forrásokból, így a main függvény elkészítése nem szükséges.
Az ERL_NIF_INIT makró segítségével inicializálhatjuk a NIF könyvtár használatát. Paraméterül meg kell adnunk az Erlang modulunk azonosítóját sztring határolók nélkül, és az implementált NIF függvényeket. Az implementált NIF függvényeket egy C-beni ErlNifFunc struktúrákat tartalmazó tömbként kell megadni. A struktúrával definiáljuk a NIF függvény Erlang oldali azonosítóját, aritását, és C oldali implementációját.
Példánkban:
A függvényparaméterek és a visszatérési értékek az ERL_NIF_TERM típus elemei. A függvényparaméterek egy argv tömbbe kerülnek, hasonlóan mint a C-s main függvényeknél már megszokhattuk. A már említett konverzió típusonként más-más függvénnyel történik, integer esetén: enif_get_int (Erlang - C) és enif_make_int (C - Erlang). Ha az enif_get_int a paraméterek alapján nem tud érvényes integer-t kiolvasni, akkor false-szal fog visszatérni. Ez esetben alkalmazhatjuk a hibakezelő függvényeket, mellyel a C oldali futást megszakítva, Erlang oldalon válthatunk ki kivételeket. Példánkban badarg kivételt fogunk kiváltani. Miután minden szükséges bemeneti paramétert C-s megfelelőjére konvertáltunk, kezdetét veheti a funkcionalitást implementáló kódrészlet. A funkcionalitás végeredményét, mint visszatérést, Erlang-os megfelelőjére kell konvertálni, majd azzal visszatérni. Nézzük meg az összeadást implementáló függvénydefiníciót:
Tegyük fel, hogy az Erlang és a C kódot a nif_demo könyvtárba mentettük le.
Egy példa az üzembehelyezésre:
Források:
Hitelesítés határozza meg, hogy mely node-ok tudnak kommunikálni egymással.
A különböző erlang node-ok egy hálózata a lehető legalacsonyabb szinten épül be a rendszerbe, minden node rendelkezik saját magic cookie-val,
ami egy erlang atom. Amikor egy node csatlakozni próbál egy másik node-hoz, akkor a magic cookie-k kerülnek összehasonlításra.
Ha nem egyeznek, akkor a csatlakoztatott node elutasítja a kapcsolatot.
Induláskor egy node-hoz hozzárendelődik egy véletlen atom, mint magic cookie, és ez a cookie más node-okhoz feltételezhetően nocookie-ként rendelődik hozzá.
Ezután az erlang hálózati authentikációs szerver (auth) első ízben a $HOME/.erlang.cookie nevű fáljt olvassa.
Ha a fájl nem létezik, akkor létrehozza és a tartalma egy véletlen string lesz.
Ha biztosak akarunk lenni benne, hogy minden lokális node-nak ugyanaz a cookie-ja, akkor beállíthatjuk manuálisan.
Ez megtehető az erlang modul set_cookie(node(), Cookie) függvényével. Így a node-ok azonos cookie-val fognak rendelkekzni és szabadon tudnak kommunikálni.
Ahhoz, hogy a Node1 node a hozzárendelt Cookie cookie-val képes legyen csatlakozni, vagy kapcsolatot fogadni a Node2 node-tól egy másik DiffCookie-val,
először a Node1-n meg kell hívni az erlang:set_cookie(Node2,DiffCookie) függvényt.
Így lehet elosztott rendszerekben kezelni a különböző felhasználói azonosítókat kezelni.
Az az alapértelmezett, hogy ha létrejön a kapcsolat két node között, akkor azonnal az összes látható node is csatlakozik.
Így mindig teljesen összefüggő hálózatot kapunk. Ha vannak node-ok különböző cookie-val, akkor ez a módszer alkalmatlan lehet,
ilyenkor a -connect_all kapcsoló értékét hamisra kell állítani.
A magic cookie a lokális node-on lekérdezhető az erlang:get_cookie() függvénnyel.
Felsorolunk néhány hasznos beépített függvényt.
erlang:disconnect_node(Node): Szétkapcsolásra kényszeríti a node-ot.
erlang:get_cookie(): Visszaadja az aktuális node magic cookie-ját.
is_alive(): True-t ad vissza,ha a rendszerben egy node kapcsolódni tud más node-okhoz, false-t ad vissza egyébként.
monitor_node(Node, true|false): egy node monitor státusza, ha az értéke igazra van állítva, akkor a Node láthatóvá válik azon a node-on ahol a függvényt meghívtuk és a másik node is látni fogja a hívó csomópontot.
node(): Visszaadja az aktuális node nevét, őrfeltételek is megengedettek.
node(Arg): Visszaadja az aktuális node-ot, ahol az Arg egy folyamat azonosító vagy referencia vagy port.
nodes(): Egy listát ad vissza minden látható node-al, ami kapcsolatban áll a hívó node-al.
nodes(Arg): Az Arg paramétertől függően visszaadhatja nem csak a látható, hanem a rejtett node-okat is, ahogy azt már korábban említettük.
set_cookie(Node, Cookie): Beállítja a magic cookie-t, akkor használjuk, amikor kapcsolódni akarunk a Node-hoz.
spawn[_link|_opt](Node, Fun): Létrehoz egy folyamatot egy távoli node-on.
spawn[_link|opt] (Node, Module, FunctionName, Args): Létrehoz egy folyamatot egy távoli node-on.
-connect_all false: Csak explicit kapcsolat beállítások kerülnek felhasználásra.
-hidden: Beállítja, hogy egy node rejtett legyen.
-name: Nevet ad egy node-nak, hosszú node neveket használ.
-setcookie Cookie: Ugyanaz, mint az erlang:set_cookie(node(), Cookie).
-sname: Nevet ad egy node-nek, de a korábbi –name-el ellentétben rövid neveket használ.
node(Arg): Visszaadja az aktuális node-ot, ahol az Arg egy folyamat azonosító vagy referencia vagy port.
nodes(): Egy listát ad vissza minden látható node-al, ami kapcsolatban áll a hívó node-al.
nodes(Arg): Az Arg paramétertől függően visszaadhatja nem csak a látható, hanem a rejtett node-okat is, ahogy azt már korábban említettük.
set_cookie(Node, Cookie): Beállítja a magic cookie-t, akkor használjuk, amikor kapcsolódni akarunk a Node-hoz.
spawn[_link|_opt](Node, Fun): Létrehoz egy folyamatot egy távoli node-on.
spawn[_link|opt] (Node, Module, FunctionName, Args): Létrehoz egy folyamatot egy távoli node-on.
Néhány hasznod modul az elosztott programozáshoz:
global: Egy globális név regisztrációs szolgáltatás.
global_group:A node-ok csoportosítása globális név regisztrációs csoportok szerint.
net_adm: Különböző erlang hálózati adminisztrációs rutinok.
net_kernel: Erlang hálózati kernel.
: Létrehoz egy folyamatot egy távoli node-on.
Ha több párhuzamosan futó node-unk van, előfordulhat, hogy egy függvényt egy másik node-on akarunk futtatni, azonban ha spawn-nal indítjuk el a folyamatot, akkor a visszatérési érték elveszik. Ha szükségünk van a függvény által visszaadott értékre, akkor használjuk az RPC szolgáltatást, ami a távoli eljárás hívási szolgáltatás(remote procedure call service). Tulajdonképpen úgy működik, mint az apply függvény, meg kell neki adni, hogy melyik node-on, melyik modul, melyik függvényént, milyen argumentumokkal akarom meghívni. Majd vagy visszaadja a függvény eredményét, vagy visszatér egy {badrpc, Reason} hibajelzéssel. Van 5 paraméteres változata is, ahol megadhatunk neki még egy timeout argumentumot is. Formailag ez így néz ki:
Fontos megjegyezni, hogy ez a szolgáltatás, csak akkor alkalmazható, ha a másik node-n futtatni kíván függvény exportálva van. Ellenkező esetben hibát fogunk kapni.
A RPC szolgáltatásra példaprogram itt található. El kell indítani két konzolt nevesítve, majd az egyiken meg kell hívni a node1:start(Node2) függvényt, ahol a Node2 a másik node neve. Majd fel kell tölteni a másik node-on az adatbázist a node1:upload(Node2) függvénnyel. Ezután már lehet lekérdezéseket indítani a node1:select(Node2,Arg) függvénnyel, ahol az Arg lehet user vagy price vagy admin vagy group. Ilyenkor a lekérdezés a másik node-on fut le, de az eredményt a hívó node kapja meg.