Web Dirbtuvės

Web Dirbtuvės

Paprastas žaidimas su go rutinomis

Golang yra google sukurta programavimo kalba pasižyminti savo sintaksės paprastumu ir greičiu. Pradedantiesiems išmokti šią programavimo kalbą labai naudinga, nes joje galima išmokti pagrindinių programavimo principų neapsikraunant pradedančiąjam sunkiai suprantamomis abstrakčiomis kodo įmantrybėmis.

Go kalboje labai aiškios pagrindinės programavimo koncepcijos : aritmetiniai, loginiai operatoriai, sąlygų operatoriai, kintamųjų tipai (statiniai, dinaminiai), ciklai, funkcijos bei jų parametrai, rodyklės (ang. pointers), atminties valdymas, atminties rūšys bei jų veikimo principai (heap, stack), masyvai, iteratoriai, lygiagretūs procesai (go routines) ir t.t.

Pristatysiu Jums įdomų ir labai paprastą pratimą kurį naudinga atlikti mokantis programuoti go kalba. Tai – skaičiavimo žaidimas naudojant go rutinas.

Įprastai programinis kodas būna vykdomas nuosekliai: jei turime dvi funkcijas pirma būna įvykdoma viena, paskui – kita. Naudojant go rutinas galime abi funkcijas paleisti vienu metu. Tai – labai svarbi lygiagretaus programavimo koncepcija.

Go rutinos (go routines) labai naudingas golang įrankis skirtas kurti lygiagrečias programas. Vienu metu veikiančios programos gali lygiagrečiai vykdyti kelis procesus. Tai ypač naudinga, kai reikia pagreitinti programos veikimą kai yra daug ilgai trunkančių operacijų. Tuomet jas galima suskirstyti į go rutinas ir paleisti vykdyti vienu metu. Taip pat tai praverčia turint daug tinklo operacijų, pavyzdžiui kai reikia siųsti ar gauti daug duomenų duomenų ar užklausų (http, tcp) iš skirtingų šaltinių, nes galima vienu metu atlikti keletą veiksmų. Tačiau būtina valdyti rutinas per duomenų kanalus, abipusę atskirtį (mutex), laukimo grupes (waitgroup) tam, kad rutinos dirbtų taip kaip tikimės ir nesipeštų.


go rutines pešasi

Iš pradžių pažvelkime kaip veikia žaidimas.

skaiciavimo zaidimas

Kaip matome, iš pradžių žaidimas paprašo suskaičiuoti matematinį veiksmą. Įvedus teisingą atsakymą programa pasveikina teisingai suskaičiavus. Įvedus neteisingą atsakymą – programa pasako, jog atsakymas netinkamas, o neįvedus atsakymo per tris sekundes programa nusprendžia, kad žaidėjas skaičiuoti nemoka. Taip pat, jei per 1 sekundę programa nesulaukia atsakymo, žaidėjas yra paskatinamas greičiau atsakyti.

O programos kodas veikia taip: yra dvi go rutinos. Viena rutina laukia žaidėjo įvesties. Tuo tarpu kita rutina skubina žaidėją pateikti atsakymą, jei jis dar nėra pateiktas po 1 sekundės nuo žaidimo pradžios. Jei gaunama įvestis arba praeina daugiau nei 3 sekundės – programa uždaroma.

Pradėkime nuo funkcijos kuri laukia vartotojo įvesties.

func ivestiesLaukimas(kanalas chan int) {
    var inputas int
    fmt.Println("Kiek bus 5*5 ??? ")
fmt.Scanf("%v", &inputas)
    mu.Lock()
   ivestiesLaukimasSignal = true
    mu.Unlock()
    kanalas <- inputas
    close(kanalas)
}

Rutinoje sukuriamas kintamasis inputas. Į jį bus gaunama žaidėjo įvesta reikšmė. fmt.Scanf nuolat klausosi ir leidžia programai pasiimti duomenis įvestus per konsolę. Gavus duomenis, abipusė atskirtis (mutex) užrakina globalų kintamąjį kuris nurodo ar yra gauta žaidėjo įvestis, šį kintamąjį padaro true ir vėl jį atrakina. Vartotojo įvesties duomenys išsiunčiami kanalu bei kanalas uždaromas.

Toliau pažiūrėkime į rutiną atsakingą už žaidėjo skubinimą:

func skubintojas(signalas chan int) {
time.Sleep(time.Second * 1)
mu.Lock()
if !ivestiesLaukimasSignal {
signalas <- 1
}
mu.Unlock()
close(signalas)
}

Ši rutina pradeda darbą miegodama vieną sekundę. Tuomet su abipuse atskirtimi (mutex) užrakina globalų kintamąjį įvestiesLaukimasSignal ir tuomet tikrina ar šis kintamasis nėra true. Jei įvestis negauta – kanalu “signalas” yra siunčiama bet kokia reikšmė. Šis kanalas neperduoda jokios informacijos, tik duoda signalą apie tai, jog įvesties dar nėra sulaukta. Tuomet atrakinamas kintamasis bei uždaromas kanalas “signalas”. Šis signalas iškeliauja į pagrindinėje funkcijoje esančią bevardę rutiną.

Svarbus go rutinų įrankis yra laukimo grupės( waitgroup). Tai yra Go programavimo kalbos sync bibliotekos įrankis, skirtas koordinuoti ir laukti, kol visos go rutinos bus baigtos vykdyti. Šis įrankis leidžia išvengti situacijų, kuriose pagrindinė vykdymo gija baigia vykdymą, prieš užbaigiant visas go rutinas, kurios buvo paleistos. WaitGroup užtikrina, kad pagrindinė vykdymo gija lauks, kol visos šoninės go rutinos bus užbaigtos. Tam, kad galėtume pasinaudoti šiuo įrankiu turime sukurti waitgroup globalų objektą.

var wg = sync.WaitGroup{}

Šis įrankis pagrindinėjė funkcijoje bus labai reikalingas.

 Pažvelkime į pagrindinę funkciją.

func main() {
kanalas := make(chan int)
signalas := make(chan int)
wg.Add(1)
go ivestiesLaukimas(kanalas)
go skubintojas(signalas)

go func() {
select {
case <-signalas:
fmt.Println("tik.. tak..")
}
}()

for {
select {
case gautasInputas, ok := <-kanalas:
if !ok {
wg.Done()
return
}
if gautasInputas == 25 {
fmt.Println("Šaunu! Skaičiuoti moki! :) ")
} else {
fmt.Println("Esi greitas, bet skaičiuoti nemoki :( ")
}
wg.Done()
return
case <-time.After(3 * time.Second):
fmt.Println("Skaičiuoti nemoki :(")
wg.Done()
return
}
}
}

Pagrindinėje (main) funkcijoje yra sukuriami du kanalai (channel): “kanalas” ir “signalas”. Kanalais go rutinų funkcijos dalinasi duomenimis. Jų pagalba rutinas galima sinchronizuoti, koordinuoti, pavyzdžiui, jei mums reikia, kad kelios rutinos užduotis vykdytų tam tikra tvarka priklausomai viena nuo kitos. Taip pat kanalais galima signalizuoti kitoms rutinoms apie svarbius įvykius, pavyzdžiui, kada jos turi baigti darbą.

Toliau pagrindinėje (main) funkcijoje pridedame vieną go rutiną į waitgroup. Tai reiškia, kad dabartinė pagrindinė vykdymo gija turės laukti vienos šoninės go rutinos, kol ji užbaigs darbą.

Paskui paleidžiamos dvi go rutinos (“ivestiesLaukimas”, “skubintojas”), bei bevardė rutina, kuri gavusi signalą iš “signalas” kanalo, parašo vartotojui skubinančią žinutę.

Toliau yra sukamas begalinis ciklas kuris savyje turi select. Jis leidžia programai stebėti kanalus ir atitinkamai reaguoti ką programa turi daryti priklausomai nuo to kokius duomenis ir iš kokio kanalo gavo.

Iš pradžių select tikrina ar “kanalas” dar atviras. Jei kanalas uždarytas (!ok), reiškia – žaidimas baigėsi ir yra pranešama pagrindinei (main) programai, kad rutina baigė veikti ir nebereikia laukti rutinų (galima užbaigti vykdyti pagrindinę funkciją) bei išeinama iš ciklo. Taip išvengiama galimų rutinų lenktynių, kai funkcija gali būti vykdoma kelis kartus, ko pasiekoje programa gali veikti netinkamai.

Toliau select tikrina gautas kanalu vertes. Jei vertė 25 – parašoma žinutė “Šaunu, skaičiuoti moki 🙂 “, o jei gaunama vertė nėra lygi 25 – parašoma žinutė “Esi greitas, bet skaičiuoti nemoki :(“. Po šių atsakymų stabdomas programos darbas.

Taip pat yra tikrinama ar dar nėra gauta reikšmė po 3 sekundžių nuo programos veikimo pradžios. Jei programa dar veikia (t.y. – negauta jokia vartotojo įvestis) – vartotojui atspausdinama žinutė “Skaičiuoti nemoki 🙁 ” ir stabdomas programos darbas .

Šis žaidimas – tai geras susipažindinimas su go rutinomis, jų sinchronizacija bei pagrindiniais įrankiais. 

Sėkmės programuojant! 🙂

Call Now Button