Konkurentnost jezika Go – I deo

Ako se iole bavite programiranjem, sigurno ste nekada želeli da ubrzate svoj kod iskorišćenjem iskorišćenjem mogućnosti savremenih višejezgarnih procesora. Jedan od načina je da izvršavanje programa rasporedite na više jezgara. Pisanje konkurentnih programa je oduvek predstavljalo izazov, sve dok na scenu nije stupio programski jezik Go i ovo učinio jednostavnijim.

Uvod

Go je programski jezik otvorenog koda razvijen od strane Google-a. Pored Google-a, veliki broj svetskih kompanija koristi Go u svom razvoju, a samo neke od njih su Facebook, DropBox, Basecamp i CloudFlare. Dva najveća projekta koji pružaju razvoj aplikacija pomoću kontejnera, Docker i Kubernetes, su napisani u programskom jeziku Go. Sintaksom je sličan ostalim skript jezicima i omogućava lak razvoj jednostavnog, pouzdanog i efikasnog softvera. Jedan od glavnih razloga zbog kojeg veliki broj vodećih svetskih kompanija koristi jezik Go, pored njegove brzine, jeste lakoća razvoja konkurentnih programa. U ovom članku akcenat će biti na osnovnim principima konkurentnosti jezika Go. Ukoliko niste imali susreta sa ovim programskim jezikom, svoje prve korake možete učiniti jednostavno na sledecem linku. Pored zvanične dokumentacije, na nekim od sledećih linkova možete naučiti više o osnovama jezika Go:

Instalacija

Pre nego što započnemo instalaciju, budite sigurni da imate instalirane najnovije verzije Ubuntu paketa na svom računaru. To možete uraditi sledećim komandama:

sudo apt-get update
sudo apt-get -y upgrade

Sada, kada ste sigurni da imate instalirane najnovije verzije paketa na računaru, preuzmite odgovarajuću arhivu sa sledećeg linka.

Preuzetu arhivu raspakujte i zatim premestite u folder /usr/local:

tar -xvf go1.11.2.linux-amd64.tar.gz
sudo mv go /usr/local

Kako bismo zapamtili putanju, potrebno je uraditi sledeće

sudo nano ~/.profile

I na kraj fajla dodati sledeće linije

export GOPATH=$HOME/work
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin

Kako bi putanja ostala zapamćena, potrebno je izvršiti sledeću komandu

source ~/.profile

Da biste bili sigurni da je instalacija uspešno protekla, u terminalu ukucajte

go version

Ukoliko je sve uspešno proteklo, dobićete povratnu informaciju o verziji programskog jezika Go koja je instalirana na računaru.

Ključna reč go

Ukoliko želite da se neka funkcija izvršava kao posebna nit, sve što treba da uradite jeste da ispred poziva funkcije dodate ključnu reč go:

func main(){
  go doSomething()
}

Sve niti se izvršavaju u istom adresnom prostoru, stoga pristup deljenoj memoriji mora biti obezbeđen. Sada ćemo razmotriti jedan jednostavan primer:

package main

import (
    "fmt"
)

func doSomething(){
    fmt.Println("do something")
}

func main() {
    fmt.Println("start")
    doSomething()
    fmt.Println("end")
}

Da biste pokrenuli dati program, potrebno je da ga sačuvate u fajlu prvi.go i u komandnoj liniji ukucate go run prvi.go. Izlaz programa je sledeći:

start
do something
end

Ovo je bilo sasvim predvidivo, jer se sve izvršava onim redom kojim je napisano. Sada ćemo omogućiti da se funkcija doSomething izvršava kao posebna nit.

func main() {
    fmt.Println("start")
    go doSomething()
    fmt.Println("end")
}

Pokretanjem izmenjenog programa verovatno ćemo dobiti sledeci izlaz:

start
end

Šta se ovde zapravo desilo? I zašto verovatno? U jeziku Go, kada se funkcija main funkcija završi, ceo program se završava. U programu iznad, nit koja izvršava funkciju doSomething uopšte nije dobila procesorsko vreme pre nego što se program završio.

Paket sync

Standardna biblioteka nam obezbeđuje paket sync koji nam pruža neka osnovna sredstva za sinhronizaciju i obezbeđivanje kritične sekcije. WaitGroup je u suštini brojač koji uvećavamo kada čekamo izvršenje neke niti, ili umanjujemo, kada je nit završila svoj posao. Tako možemo odložiti izvršavanje određenog dela programa dok vrednost brojača ne bude nula, što bi značilo da su sve niti završile svoj posao. Izmenite prethodni program da izgleda kao sledeći:

package main

import (
  "fmt"
  "sync"
)

var wg sync.WaitGroup

func main() {
  fmt.Println("start")
  wg.Add(1) // govori da želimo da čekamo jednu nit da završi svoj posao
  go doSomething()
  fmt.Println("end")
  wg.Wait() // čekamo da nit završi svoj posao
  // kraj programa
}
func doSomething() {
  fmt.Println("do something")
  wg.Done() // nit je završila svoj posao
}

Objašnjenje ključnih delova koda:

  • var wg sync.WaitGroup – definišemo WaitGroup objekat čija je početna vrednost nula,
  • wg.Add(1) – govorimo da postoji nit čije je izvršenje potrebno sačekati (u našem slučaju funkcija doSomething),
  • wg.Wait() – govori programu da se blokira sve dok WaitGroup brojač ne dostigne vrednost nula,
  • wg.Done() – govori da je nit izvršila svoj posao.

Pokrenite ponovo program i verovatno ćete dobiti sledeći izlaz:

start
end
do something

Ovog puta je glavni program sačekao da se izvrši pozadinska nit i onda prekinuo svoje izvršavanje. Međutim, redosled izvršavanja je i dalje nepredvidiv. Kada zatražimo da se program izvršava konkurentno, ne možemo tačno reći kako i kada će se instrukcije izvršiti, što može dovesti do nepredvidivog redosleda izvršavanja. Sve dok razumemo zašto se ovo dešava, to ne predstavlja problem kod razvoja konkurentnih programa. Videli smo da osnovni razvoj konkurentnog programa u jeziku Go nije preterano težak, jer sve što treba da uradite jeste da dodate ključnu reč go. U narednom delu ćemo obraditi naprednije koncepte konkurentnosti jezika Go i sve to objasniti na adekvatnim primerima.

Korisni linkovi

Autor: Nikola Pajovic

Student prve godine master studija informatike na Prirodno-matematičkom fakultetu u Kragujevcu.

Tokom studija bio je polaznik programa praksi u kompanijama Comtrade i Quantox Technology.

Nikola Pajovic

Student prve godine master studija informatike na Prirodno-matematičkom fakultetu u Kragujevcu. Tokom studija bio je polaznik programa praksi u kompanijama Comtrade i Quantox Technology.