Go¶
Csővezetékes rendezés¶
A funkcionális dekompozíció tipikus példája az úgynevezett csővezeték. Ennél egy láncban helyezzük el a folyamatokat, melyek egy feldolgozás munkafázisait végzik el a rajtuk áthaladó adatokon. Egyidőben több adatelem van feldolgozás alatt, mindegyik másik munkafázisban. Egy adatelemnek a teljes feldolgozáshoz végig kell járnia sorrendben az összes munkafázist. Sebességnövekedést úgy érünk el, ha sok feldolgozandó adatelemünk van, ezek összesített feldolgozási ideje lényegesen lecsökkenhet csővezeték alkalmazásával. Ideális esetben n fázis esetén n-szeres gyorsulásra számíthatunk akkor, ha a fázisok időigénye megközelítőleg azonos, hiszen a leglassabb fázis határozza meg az adatok áramlási sebességét.
Az egyik legegyszerűbb alkalmazása a csővezetékes rendezés, a csővezeték hossza ebben az esetben megegyezik a rendezendő elemek számával. Minden fázis ugyanazt a munkafolyamatot hajtja végre, a rajta áthaladó sorozat elemeinek sorrendjét megváltoztatja úgy, hogy a nagyobb elemek a sorozat vége felé helyezkedjenek el.
A send
folyamat n db karaktert fog beolvasni és számmá konvertálja. Ez fogja a csővezeték első fázisára ráküldeni a rendezendő számokat. A receive
utasítás pedig a csővezeték utolsó fázisáról fogja a standard output-ra küldeni a rendezett listát. A pipesort
folyamat számot olvas be az input csatornáján és ugyanannyit küld tovább a kimenő csatornáján. A már látott számok közül a legnagyobbat eltárolja. Minden újabb beérkező számot összehasonlít az addigi legnagyobbal, a nagyobbikat eltárolja, a kisebbiket továbbküldi. Miután az összes számot látta, a lokálisan tárolt legnagyobbat is elküldi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
Multiplexer¶
A select legtipikusabb alkalmazása az úgynevezett multiplexer folyamat megvalósítása. Ennek az a feladata, hogy több bemenő csatorna forgalmát egy kimenő csatornára irányítsa. Sokszor együtt alkalmazzák az úgynevezett demultiplexer folyamattal, ami egy bemenő csatornán érkező adatokat oszt szét több kimenő csatornán. A két folyamat együttes alkalmazásával elérhető, hogy egy fizikai csatornát több logikai csatornaként használjunk, ezt a technikát nevezik multiplexálásnak.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
|
A sync könyvtár¶
Korábban már láttunk példát arra, hogy a goroutine
-ok hozzáférhetnek a közös változókhoz is, pl. a program idő előtti tárminálását megakadályozó WaitGroup-hoz, amit package szintén, egy globális változó segíségével értek el. Ez tehát azt jelenti, hogy ilyenformán a Go alkalmas közös memóriás párhuzamos programozásra is. Az ehhez szükséges párhuzamos konstrukciókat a sync könyvtár függvényei is biztosítják, amelyek a korábban megismert közös memóriás párhuzamos konstrukciókat valósítanak meg.
Azt láttuk eddig, hogy a goroutine-ok között csatorna segítségével megvalósítható a szinkron és aszinkron kommunikáció. De vannak esetek, amikor igazából nincs szükség kommunikációra az egyes folyamatok között, így tehát hogy biztosítsuk a goroutine-ok számára a konfliktusmentes hozzáférést 1-1 globális vagy paraméterben kapott erőforráshoz, kölcsönös kizárást kell alkalmaznunk. Fontos, hogy amennyiben goroutine-ok szinkronizációját szeretnék biztosítani, továbbra is csatornát érdemes használni.
Ezenkívül használjuk az atomic könyvtárat is, ami hasznos atomi változókat biztosít.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Mutex lényegében a korábban már látott explicit zár, amivel megvalósítható a kölcsönös kizárás. Van egy impliciten tárolt értéke, ha ez 0-t vesz fel, akkor a zár nem blokkol, ellenkező esetben blokkol. 2 függvénye van:
-
Lock()
: Zárol, tehát ha a Mutex már használatban van, akkor az adott goroutine blokkolódik. -
Unlock()
: Feloldja a zárt.
Önmagában a Mutex talán a Java synchronized kulcsszavára hasonlít, wait és signal műveletek nélkül.
A wait és a signal műveleteket a Cond típus biztosítja, amellyel feltételváltozót tudunk létrehozni. Feltételváltozókat egy korábban létrehozott Mutex segítségével tudunk létrehozni.
-
Wait()
: Feloldja a Mutex-et, az aktuális goroutine futását pedig felfüggeszti. Hasonlóan a Java-hoz, itt is ciklusban érdemes vizsgálni a feltételt komplexebb problémák esetén, hogy elkerüljük a várakoztatási feltétel megsértését. -
Signal()
: Felébreszt egy blokkolt goroutine-t -
Broadcast()
: Felébreszti az összes blokkolt goroutine-t (Java signalAll()/notifyAll())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
|
Gyakorló feladat¶
I) Valósítsd meg az étkező filozófusok problémát a sync konstrukciók segítségével.