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 a 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 számok - a csővezetéknek köszönhetően már rendezett sorrendben. A pipesort
folyamat folyamatosan fogadja a számokat: az elsőt eltárolja, majd beolvassa a következőt és összehasonlítja a meglévővel. A nagyobbik érték megmarad a következő összehasonlításhoz, a kisebbiket továbbküldi a következő pipesort
goroutine-nak. Miután az összes szám beolvasásra került, a lokálisan tárolt legnagyobb értéket is továbbkü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ára valamilyen logika alapjá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ée, egy globális változón keresztül é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 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.
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, hogy biztosítsuk a várakoztatási feltétel ismételt ellenőrzését egy-egy lehetséges felélesztést követően. -
Signal()
: Felébreszt egy blokkolt goroutine-t -
Broadcast()
: Felébreszti az összes blokkolt goroutine-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 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¶
(1) Írj egy párhuzamosan futó Go programot, amely a standard input-ról bájtonként olvas be (bufio.Reader segítségével). A goroutine-ok egyirányú, kétszeresen bufferelt, bool típusú csatorna segítségével kommunikáljanak. A program a '!' beolvasását követően termináljon.
-
A folyamat: csak a < és > karaktereket engedi át a (b) folyamatnak
-
B folyamat: < esetén +1-gyel, > esetén -1-gyel növeli egy 0-ra inicializált változó értékét, majd a terminálás előtt írja ki a szám aktuális értékét
Biztosítsd WaitGroup segítségével, hogy a main függvény ne termináljon idő előtt.