A Go nyelv a konkurenciát a legtöbb esetben üzenetküldéssel, és nem osztott memória használatával valósítja meg. Erre a nyelvbe beépített csatorna típust használja. A csatornán keresztüli kommunikáció a szinkronizáció egyetlen módja a nyelvben.
A gorutinok a legfontosabb, konkurenciát támogató eszközök a nyelvben. Ezek olyan függvények, amik közös címtérben, a többi gorutinnal párhuzamosan futnak. A fogalom hasonlít például a szálakhoz vagy a folyamatokhoz, de egyikkel sem egyezik meg pontosan.
A gorutinok az operációs rendszer különböző szálaira képződhetnek le, de ezt a nyelv kezeli, a komplexitás nagy részét elrejti a programozó elől. Egy függvényhívás új gorutinban indul, ha a neve elég a go
kulcsszót tesszük. Azaz:
Például:
Ilyenkor a rendezés külön gorutinban indul, nem blokkolja az őt meghívó gorutin futását. Amikor a hívott függvény visszatér, a gorutin automatikusan megszűnik. A visszatérési értékek, ha voltak, eldobásra kerülnek.
A go
kulcsszó után függvény literált is írhatunk. Mivel ezek zárványok, a változókat, amiket a gorutin használ, nem szabadítja fel a szemétgyűjtő.
Különböző gorutinok közti szinkronizációra és kommunikációra csatorna változókon keresztül van lehetőség (ld. Csatorna fejezet). Az előző példa, szinkronizációval kiegészítve:
Az adatok fogadása egy csatornáról blokkol, ha a csatorna üres. Adatok küldése nem bufferelt csatornáknál akkor blokkol, ha már van valami a csatornán, egyébként csak ha megtelt a buffer.
A csatornák szemaforként is használhatók, ha például egy buffer nélküli csatornára egy gorutin egy értéket tesz, a kritikus szekció után pedig leveszi azt, a függvény többi példánya nem léphet be vele egyszerre a kritikus szekcióba, mert a csatorna tele van, azaz blokkol. Bufferelt csatornával megvalósítható, hogy tetszőleges számú gorutin lehessen egyszerre a szekcióban.
Mivel a csatornákon akár csatornákat is át lehet küldeni, gyakori, hogy a dolgozó gorutin ilyen módon kapja meg azt a csatornát, amelyiken az eredményt vissza kell küldenie a hívónak.
A leírás készítésekor (2012. május), a nyelv implementációi alapbeállításként nem párhuzamosítják a programokat. Ha több processzort is ki akarunk használni, azt explicit közölni kell a fordítóval vagy a futtató környezettel. Ha n
darab processzort szeretnénk használni, a következőket tehetjük:
GOMAXPROCS
környezeti változó értékét állítsuk n
-re, vagyruntime
csomagot, és hívjuk meg a runtime.GOMAXPROCS(n)
függvényt.
Segítségünkre lehet a runtime.NumCPU()
függvény, ami a gépen található logikai processzorok számát adja vissza.
Sajnos a követelmény még a Go v1.0-ban is fennáll, de a nyelv későbbi implementációiban várhatóan megszűnik majd.
Az atomic
csomag alacsonyszintű atomi primitív memóriaműveleteket nyújt, melyek szinkronizációs algoritmusok megvalósításánál lehetnek hasznosak.
A csomagban található műveletek használata nagy odafigyelést igényel, ezért azok általános célú használata kerülendő, helyette csatornákon keresztüli kommunikációval érdemes szinkronizálni. A pontos magyarázata nem található meg a csomag dokumentációjában, az ok ami miatt ez a figyelmeztetés jelen van az, hogy ezek az utasítások önmagukban véve nem jól szinkronizáltak, csak annyit garantálnak, hogy a memóriaterületeken elvégzett műveletek nem interferálnak párhuzamosan végrehajtódó utasításokkal, viszont a műveletek eredményének a goroutine-okban való láthatósága (observing) nem garantált, azaz az elvégzett írás nem áll happens-before relációban az esetleges későbbi olvasásokkal.
A lentebb található függvények több típusra is elérhetőek azonos jelentéssel, ezért csak a szignatórájuk sémáját írom le. Ezekre a típusokra elérhetőek:
Atomi összeadásművelet (a 32 bites adatmozgatás atomicitása architektúránként változhat, ezért erre is meg kellett valósítani).
A neki megfelelő nem atomi utasítássorozat:
Egy adott memóriacímen felcserél két értéket, hogyha a címen található érték megegyezik az átadott régi értékkel, majd visszatér a csere sikerességét jelentő bool értékkel.
A neki megfelelő nem atomi utasítássorozat:
Az addr által mutatott memóriacímen található érték atomi betöltése és visszaadása visszatérési értékként.
A val változó értékének atomi eltárolása az addr memóriacímen.
A CompareAndSwap ellenőrzés nélküli változata, egy addr által mutatott címre betölti az új értéket és visszatér a korábban ott lévő értékkel.
A neki megfelelő nem atomi utasítássorozat:
A Cond típus a goroutine-ok közötti randevú típusú szinkronzációt valósít meg. Egy eseményre várakozást, vagy az adott esemény bejelentését (várakozó goroutine-ok értesítését) teszi lehetővé. Egy Cond objektumot a használatának megkezdése után nem szabad másolni, csak cím szerint átadni.
NewCond(l Locker) *Cond
- Létrehoz egy Cond objektumot a paraméterként átadott zároló objektummal (lásd lentebb)(*Cond) Broadcast()
- A várt esemény bekövetkeztének bejelentése minden várakozónak, a blokkolt goroutine-ok felébresztése, hogy ne várakozzanak tovább.(*Cond) Signal()
- A várt esemény bekövetkeztének bejelentése, viszont itt csak egyetlen várakozó számára.(*Cond) Wait()
- Várakozás az esemény bejelentésére, az ezt meghívó goroutine blokkolódik és alvó állapotba kerül, amíg a randevúpont másik oldalán álló goroutine Broadcast()-ot nem hív, vagy Signal()-t és a felébresztésre választott goroutine pont a miénk.A Once
osztály egy művelet számára biztosítja, hogy az csak egyetlen egyszer fut le még konkurrens környezetben is.
(*Once) Do(f func())
- A paraméterként kapott függvényt hajtja végre, hogyha az objektumon amit hívunk még nem hívódott meg a Do() metódus.(m *Mutex) Lock()
- Az m Mutex zárolása, vagy ha már zárolt, akkor a goroutine blokkolása amíg nem sikerül zárolni.(m *Mutex) Unlock()
- Az m Mutex zárolásának feloldása. Egy zárolt Mutex objektum nincs az őt zároló goroutine-hoz kötve, így másik goroutine is feloldhatja a zárolást. Ha egy nem zárolt mutexet akarunk feloldani, az runtime error-t eredményez.Olvasási és írási szintek elkülönítése, az RWMutex-ünkkel védeni kívánt objektumhoz tetszőleges számú olvasó hozzáférhet egyszerre, az olvasások csak az írást zárják ki, viszont ha írásra zárolják, akkor az minden további írást vagy olvasást is kizár a feloldásig. Az olvasási zár írási zárrá upgrade-je nem támogatott, a Go 1 memóriamodelljében és az RWMutex jelenlegi implementációjában nem valósítható meg atomi RUnlockAndLock művelet (részletek).
(rw *RWMutex) RLock()
- Az rw mutex zárolása olvasásra, ha korábban volt olyan goroutine, ami írásra zárolta, akkor blokkol. Korábbi olvasási zárolások esetén nem blokkol.(rw *RWMutex) RUnlock()
- Az rw mutex olvasási zárolásának feloldása.
(rw *RWMutex) Lock() - Az rw mutex zárolása írásra, ha az már zárolva van írásra vagy olvasásra, akkor blokkol, egyéb esetben sikeres a zárolás.
(rw *RWMutex) Unlock() - Az rw mutex írási zárolásának feloldása.(wg *WaitGroup) Add(delta int)
- A wg várakozási csoport számlálójának növelése a delta értékével.(wg *WaitGroup) Done()
- A befejezés jelzése, a számláló csökkentése 1-gyel.(wg *WaitGroup) Wait()
- Várakozás, amíg várakozási csoport számlálója 0-ra nem csökken.