A Cilk programozási nyelv

Párhuzamosság

A Cilk egy többszálú (thread) párhuzamosságot támogató nyelv. Mivel a tervezési elvek között a programozó tehermentesítése is szerpelt, így a párhuzamos futáshoz szükséges mindenfajta erőforráskezelést és kommunikációt a futtató rendszer végzi, a programozó számára rejtve.

Az alapvető párhuzamos programhoz három kulcsszó kell csak: cilk, spawn, sync. A cilk kulcsszó jelöli a párhuzamosan futtatható függvényeket, amelyek minden másban megegyeznek a C függvényekkel. A spawn paranccsal hívható meg egy cilk függvény, a hívó eljárás futása ekkor nem függesztődik fel, mint a C esetében, hanem párhuzamosan folytatódik a meghívott mellett, akár újabb spawn paranccsal. Az így elindított gyermek eljárássokkal való szinkronizációra pedig a sync utasítás használandó; a sync utasítás végrehajtása csak akkor fejeződik be, ha az adott eljárás összes párhuzamosan indított gyermek eljárása befejeződött már. Minden cilk függvénybe a futtató rendszer a return utasítások elé egy implicit sync-et is berak, ezzel is a programozót segítve.

A Cilk programok felfoghatók, mint egy irányított, körmentes gráf. A gráf pontjai a Cilk függvények soros végrehajtású részei, amelyek határait a spawn és sync utasítások adják. Ezek az utasítások az éleknek felelnek meg a gráfban.
A párhuzamos programokat egy processzoron futtatva az ütemező mélységi bejárást végez. Több processzor esetén ha egy processzornak nincs munkája, akkor egy másik processzor feladatvermében lévő legöregebb szálat veszi el. (Bár így a verem elnevezés nem épp szerencsés, hisz mindkét oldaláról elérhető). Míg a futtató környezet biztosítja a processzorok optimális kihasználását, a rendszer nem tartalmaz védelmet a zárak (lock) használata miatt bekövetkezhető holtpontok ellen, ez a felelősség a programozóé.

Alapesetben egy cilk függvény visszatérési értékét egy változóban tároljuk a szülő keretében (frame). Ha ezeket az értékeket egy másik függvény paramétereként szeretnénk felhasználni, akkor azt szintén a szülő keretében inlet-ként kell definiálnunk.
Ha egy eljáráson belül biztosak szeretnénk lenni, hogy az innen indított függvények visszatérési értéke rendelkezésre áll, akkor szinkronizálni kell (sync;). Ez garantálja, hogy az innen indított szálak a szinkronizáció végrehajtása utánra biztosan befejeződnek.
Előfordulhat, hogy feleslegesen indítottunk el egy szálat, és hatékonysági szempontból szeretnénk megállítani. Erre szolgál az abort. Ennek hatására a már elindított gyermekek terminálnak (lehet, hogy normálisan). A SYNCHED változó segítségével le lehet kérdezni, hogy az aktuális eljárásnak vannak-e még futó gyermekei, anélkül, hogy szinkronizálnánk. Ha ez az érték 1, akkor az ütemező garantálja, hogy nincs befejezetlen gyermeke.

Az osztott memória miatt a Cilk bevezette a cilk_fence() primitivet, mely biztosítja, hogy minden memória művelet egy processzoron végrehajtásra kerüljön, mielőtt a következő utasításra kerülne a vezérlés.

Locking: A Cilk könyvtárban biztosít egy Cilk_lockvar típust és a hozzá tartozó műveleteket, amellyel atomi végrehajtási szakaszokat lehet létrehozni.

#include <cilk-lib.h> Cilk_lockvar mylock; { Cilk_lock_init(mylock); Cilk_lock(mylock); /*kritikus szakasz*/ Cilk_unlock(mylock); }