Rozhraní, metody, gorutiny a kanály v programovacím jazyku Go

11. 12. 2018
Doba čtení: 37 minut

Sdílet

 Autor: Go Lang
Mezi užitečné technologie, které nalezneme v jazyku Go, patří podpora datového typu rozhraní (interface) a především pak takzvané gorutiny a kanály. Právě s těmito třemi důležitými technologiemi se dnes podrobněji seznámíme.

Obsah

1. Rozhraní, metody, gorutiny a kanály v programovacím jazyku Go

2. Metody

3. Metody s parametry

4. Předání ukazatele na strukturu (objekt) do volané metody

5. Rozhraní (interface)

6. Rozhraní jako datový typ při volání funkcí

7. Metody a rozhraní (struktura vyhovující rozhraní)

8. Vícenásobná implementace stejného rozhraní

9. Řez se strukturami implementujícími společné rozhraní

10. Řezy rozhraní jako parametry funkce

11. Datový typ implementující větší množství rozhraní

12. Skládání rozhraní

13. Gorutiny

14. Spuštění gorutiny

15. Spuštění většího množství gorutin na pozadí

16. Kanály – technologie pro komunikaci mezi gorutinami

17. Implementace workerů s využitím gorutin a kanálů

18. Deadlock a jeho detekce v runtime

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Rozhraní, metody, gorutiny a kanály v programovacím jazyku Go

Většina vlastností programovacího jazyka Go, s nimiž jsme se alespoň prozatím seznámili, ve skutečnosti nebyla nijak revoluční – kromě automatické správy paměti (GC) a plnohodnotné podpory Unicode se vlastně jednalo o typový systém převzatý z céčkovských jazyků a vylepšený o mnohem silnější typovou kontrolu, řezy, pole s kontrolou mezí, řetězce (ty jsou zde plnohodnotným datovým typem) a samozřejmě o nové datové typy (zejména o mapy). Mezi další vylepšení, s nímž jsme se již seznámili, patří možnost vrátit větší množství hodnot z funkcí, což využijeme i v dnešních příkladech.

Jazyk Go se ovšem stal populární i z jiných důvodů. Především se v jeho typovém systému mohou používat rozhraní (interface) a kromě toho Go podporuje tvorbu takzvaných gorutin, což jsou funkce běžící asynchronně k hlavnímu vláknu (někdy se setkáme s označením light-weight process). Mezi jednotlivými gorutinami je možné realizovat komunikaci s využitím další technologie – takzvaných kanálů (channel). Kanály lze využít jak k běžné synchronizaci, tak i k realizaci front se zprávami (message queue). Užitečný a v kontextu mainstreamových jazyků i přelomový je však fakt, že gorutiny i kanály lze v programech použít velmi snadno; stačí pouze pochopit základní koncept, klíčové slovo go a operátor ← .

Poznámka: koncept kanálů byl převzat i do dalších jazyků. Na stránkách Rootu jsme se již seznámili s knihovnou core.async implementovanou v programovacím jazyku Clojure [1] [2] [3].

Právě popisu rozhraní, gorutin a kanálů bude věnován dnešní článek, i když složitější příklady budou vysvětleny až příště.

2. Metody

Nejdříve se seznámíme s konceptem metod, jež slouží pro implementaci funkcionality svázané s nějakým typem záznamu (struktury). S metodami se setkáme v dnes již klasických programovacích jazycích podporujících objektově orientované programování – v takovém případě jsou metody svázány s nějakou třídou popř. se jejich hlavičky mohou objevit v rozhraních. Ovšem v programovacím jazyku Go se s třídami nesetkáme (s rozhraními ovšem ano). Metody zde jsou implementovány jako funkce u kterých je kromě jména, parametrů a typů návratových hodnot specifikován i takzvaný receiver (příjemce), což je v praxi právě datový typ záznam (resp. struktura) popř. se alternativně může jednat o ukazatel na typ záznam.

Příjemce je – což je specifické právě pro Go – zapisován ještě před jméno metody. Podívejme se na několik příkladů. Nejdříve si uvedeme běžnou funkci bez parametrů vracející hodnotu typu float64:

func length() float64 {
        ...
        ...
        ...
}

Metoda s příjemcem typu Line bude vypadat odlišně, protože se před jméno metody musí zapsat právě specifikace příjemce (dvojice identifikátor + typ umístěná do kulatých závorek):

func (line Line) length() float64 {
        ...
        ...
        ...
}

Pokud nyní vytvoříme typ záznam pojmenovaný Line:

type Line struct {
        x1, y1 float64
        x2, y2 float64
}

…můžeme metodu length zavolat s využitím běžné tečkové notace, která se mj. používá i v běžných objektově orientovaných jazycích:

line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
line_length := line1.length()

Povšimněte si, že se v těle metody pracuje s příjemcem line stejně jako s běžným parametrem – interně se totiž o parametr skutečně jedná (v podstatě jde o obdobu this v Javě či self v Pythonu):

func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}

Podívejme se nyní na úplný zdrojový kód dnešního prvního demonstračního příkladu, v němž je deklarována struktura/záznam nazvaná Line a taktéž metoda length svázaná s touto strukturou:

package main
 
import (
        "fmt"
        "math"
)
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
 
        line_length := line1.length()
        fmt.Println(line_length)
}

Po spuštění tohoto demonstračního příkladu by se měla nejdříve zobrazit výchozí hodnota záznamu typu Line tak, jak byla inicializovaná a na druhém řádku vypočtená délka úsečky:

{0 0 100 100}
141.4213562373095

Metody se od funkcí liší ještě v jednom směru – jejich jméno nemusí být v daném modulu unikátní, protože specifikace příjemce stačí sama od sebe na odlišení metody pro různé typy i na odlišení metody od funkce. Následující příklad je tedy zcela korektní:

package main
 
import (
        "fmt"
        "math"
)
 
type Point struct {
        x1, y1 float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length() float64 {
        return 0
}
 
func (point Point) length() float64 {
        return 0
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
 
        line_length := line1.length()
        fmt.Println(line_length)
}

3. Metody s parametry

Vyzkoušejme si nyní vytvořit metodu s parametry, aby byl ještě více zřejmý jak zápis, jakým se metoda deklaruje ve zdrojovém kódu, tak i technika jejího volání v čase běhu programu (runtime). Parametry metody se zapisují do kulatých závorek za jménem metody, tedy stejně, jako u běžných funkcí. Následující metodě se při jejím volání předá jak vlastní příjemce typu Line, tak i dvojice parametrů dx a dy, které jsou typu float16:

func (line Line) translate(dx, dy float64) {
        ...
        ...
        ...
}

Vzhledem k tomu, že příjemcem je běžná datová struktura (neboli záznam), mohlo by se zdát, že implementace metody pro posun úsečky v rámci souřadného systému je triviální – ostatně přístup k položkám struktury máme a víme, že jsou měnitelné (mutable):

func (line Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", line, dx, dy)
        line.x1 += dx
        line.y1 += dy
        line.x2 += dx
        line.y2 += dy
}

Ve skutečnosti je to však nepatrně složitější, a to z toho důvodu, že v programovacím jazyce Go se při volání funkcí i metod parametry předávají hodnotou a nikoli odkazem. To znamená, že příjemce, který je interně metodě předán jako skrytý parametr, je naklonovanou kopií původní struktury a navenek se tedy změna souřadnic nijak neprojeví (pouze uvnitř metody uvidíme nové hodnoty souřadnic).

Můžeme se o tom snadno přesvědčit po překladu a spuštění tohoto demonstračního příkladu, který sice neskončí s chybou, ovšem po návratu z metody translate budou souřadnice v záznamu line nezměněny:

package main
 
import (
        "fmt"
        "math"
)
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func (line Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", line, dx, dy)
        line.x1 += dx
        line.y1 += dy
        line.x2 += dx
        line.y2 += dy
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
        line1.translate(5, 5)
        fmt.Println(line1)
 
        line_length := line1.length()
        fmt.Println(line_length)
}

Výsledek, který získáme po spuštění tohoto příkladu ukazuje, že se položky struktury vně metody skutečně nezměnily (viz první a třetí řádek):

{0 0 100 100}
Translating line {0 0 100 100} by 5.000000 5.000000
{0 0 100 100}
141.4213562373095
Poznámka: toto chování jazyka Go nám sice může připadat poněkud zvláštní, ovšem na druhou stranu je dobré vědět, že je jazyk (alespoň co se týče práce s hodnotami a voláním funkcí i metod) zcela konzistentní a pracuje se všemi hodnotami stejným způsobem. Naproti tomu například v Javě se chování liší podle toho, zda pracujeme s takzvanými primitivními datovými typy (ty se předávají hodnotou) či s objektovými typy (ty se předávají odkazem – referencí).

4. Předání ukazatele na strukturu (objekt) do volané metody

Jakým způsobem se tedy musí postupovat, když budeme chtít ve svém programu implementovat metodu, která mění stav záznamu, pro který byla vytvořena? Řešení je ve skutečnosti velmi jednoduché – namísto předání struktury hodnotou, tedy tak, jak jsme to udělali v předchozím příkladu:

func (line Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", line, dx, dy)
        line.x1 += dx
        line.y1 += dy
        line.x2 += dx
        line.y2 += dy
}
 
line1.translate(5, 5)

…předáme do metody ukazatel na strukturu. To znamená nutnost změny samotné metody (odlišný typ příjemce a odlišný přístup k prvkům záznamu), ovšem samotné její volání bude stejné (!) jako v předchozím příkladu (nemusíme zjišťovat adresu struktury pomocí unárního operátoru &):

func (line *Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", *line, dx, dy)
        (*line).x1 += dx
        (*line).y1 += dy
        (*line).x2 += dx
        (*line).y2 += dy
}
 
line1.translate(5, 5)

Takto upravený demonstrační příklad již bude provádět všechny operace takovým způsobem, jak jsme si přáli:

package main
 
import (
        "fmt"
        "math"
)
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func (line *Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", *line, dx, dy)
        (*line).x1 += dx
        (*line).y1 += dy
        (*line).x2 += dx
        (*line).y2 += dy
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
        line1.translate(5, 5)
        fmt.Println(line1)
 
        line_length := line1.length()
        fmt.Println(line_length)
}

Na třetím řádku je patrné, že úsečka byla skutečně v rovině posunuta o pět jednotek doprava a nahoru:

{0 0 100 100}
Translating line {0 0 100 100} by 5.000000 5.000000
{5 5 105 105}
141.4213562373095

Ve skutečnosti je však v programovacím jazyce Go možné napsat metodu ještě jednodušeji – pouhou změnou typu příjemce (na ukazatel), nikoli změnou samotného těla metody:

func (line *Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", *line, dx, dy)
        line.x1 += dx
        line.y1 += dy
        line.x2 += dx
        line.y2 += dy
}

Výše uvedený zápis je zajímavý, protože sice předáváme záznam typu Line přes ukazatel, ovšem v těle metody přistupujeme k položkám struktury pouze s použitím tečkové notace a nikoli přes (*line).položka. Důvod pro toto nepochybně praktické chování (které není například v C či C++ možné) jsme si již vysvětlili ve dvanácté kapitole předchozí části <a>tohoto seriálu.

Podívejme se nyní na upravený příklad, který vznikl zjednodušením příkladu předchozího:

package main
 
import (
        "fmt"
        "math"
)
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func (line *Line) translate(dx, dy float64) {
        fmt.Printf("Translating line %v by %f %f\n", *line, dx, dy)
        line.x1 += dx
        line.y1 += dy
        line.x2 += dx
        line.y2 += dy
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
        line1.translate(5, 5)
        fmt.Println(line1)
 
        line_length := line1.length()
        fmt.Println(line_length)
}

Výsledkem budou stejné zprávy, jako v předchozím příkladu:

{0 0 100 100}
Translating line {0 0 100 100} by 5.000000 5.000000
{5 5 105 105}
141.4213562373095
Poznámka: v Go je tedy velmi snadné rozlišit mezi metodami, které nikdy nemění stav příjemce a mezi metodami, které ho mění. Taktéž se můžeme sami rozhodnout, že pokud je struktura s příjemcem rozsáhlá (stovky bajtů), nemá smysl provádět kopii při každém volání metody a změnou jediného znaku použít volání odkazem/referencí.

5. Rozhraní (interface)

Ve druhé části článku se seznámíme s konceptem takzvaných rozhraní (interface). Jedná se o předposlední datový typ, s nímž se setkáme (posledním typem je kanál zmíněný v závěru článku). Rozhraní v jazyku Go byla inspirována protokoly, s nimiž jsme se mohli setkat například v programovacím jazyku Smalltalk: ve stručnosti jde o specifikaci metod (jmen, parametrů, návratových typů), které jsou společné pro entity s nějakou sdílenou vlastností nebo vlastnostmi. V rozhraní se však nijak nespecifikuje vlastní chování, tj. těla metod. V Go navíc není nutné explicitně určovat, které záznamy implementují dané rozhraní – tuto informaci si dokáže automaticky odvodit překladač (poněkud nepřesně se toto chování nazývá duck typing).

Poznámka: v jazyce Java se taktéž s rozhraními pracuje, ovšem zde je nutné explicitně určit, které třídy rozhraní implementují. V Go se tento princip neuplatňuje, už jen z toho důvodu, že se zde vůbec s pojmem třída nepracuje.

Při deklaraci nového rozhraní (tj. při vytváření nového datového typu) je nutné specifikovat jak jméno rozhraní, tak i seznam hlaviček metod, které jsou součástí rozhraní (tento seznam ovšem může být prázdný, nicméně je nutné ho zapsat pomocí prázdného bloku {}). Příkladem rozhraní s jedinou metodou může být datový typ pojmenovaný OpenShape, v němž je předepsána jediná metoda length bez parametrů a s návratovou hodnotou float64 (u metody předepsané v rozhraní se ovšem neuvádí příjemce – ten si Go odvodí automaticky na základě dalšího kódu):

type OpenShape interface {
        length() float64
}

V rozhraní může být předepsáno větší množství metod:

type ClosedShape interface {
        area() float64
        perimeter() float64
}

Nebo naopak nemusí být předepsána žádná metoda:

type Shape interface {
}
Poznámka: v seriálu o programovacím jazyku Rust jsme se setkali s termínem trait (rys). Traity lze (zjednodušeně řečeno) pokládat za rozšířená rozhraní, která kromě hlaviček funkcí a metod obsahují (resp. mohou obsahovat) i jejich těla, ale už nikoli stavové informace. Právě tím, že se v traitu mohou objevit implementace metod, se tento koncept odlišuje od běžných rozhraní (je jejich zobecněním).

Následuje příklad, v němž je pouze deklarována trojice rozhraní:

package main
 
type Shape interface {
}
 
type OpenShape interface {
        length() float64
}
 
type ClosedShape interface {
        area() float64
        perimeter() float64
}
 
func main() {
}

6. Rozhraní jako datový typ při volání funkcí

Rozhraní jakožto plnohodnotný datový typ je možné použít pro specifikaci typu parametru (parametrů) ve funkcích. Opět si to vyzkoušejme na našem příkladu s rozhraním nazvaným OpenShape, v němž je předepsána jediná metoda length():

type OpenShape interface {
        length() float64
}

Nyní můžeme napsat funkci (běžnou funkci), které se předá libovolná struktura implementující rozhraní OpenShape a tato funkce vrátí hodnotu získanou zavoláním metody OpenShape.length():

func length(shape OpenShape) float64 {
        return shape.length()
}
Poznámka: nenechte se zmást tím, že můžeme mít funkci length a metodu (či více metod) length. Víme již, že se jedná o odlišné prvky programu, takže funkci length klidně můžeme přejmenovat:
func compute_open_shape_length(shape OpenShape) float64 {
        return shape.length()
}

V šestém demonstračním příkladu se pokusíme funkci length() zavolat a předat jí strukturu/záznam Line:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length(shape OpenShape) float64 {
        return shape.length()
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
 
        line_length := length(line1)
        fmt.Println(line_length)
}

Ovšem vzhledem k tomu, že struktura Line prozatím rozhraní OpenShape neimplementuje (v Go se mluví o tom, že struktura nevyhovuje rozhraní), nebude možné program spustit:

./06_interface_implementation.go:12:2: imported and not used: "math"
./06_interface_implementation.go:33:23: cannot use line1 (type Line) as type OpenShape in argument to length:
        Line does not implement OpenShape (missing length method)

7. Metody a rozhraní (struktura vyhovující rozhraní)

Co přesně tedy musíme udělat pro to, aby struktura Line vyhovovala (satisfy) rozhraní OpenShape a v něm předepsané metodě length()? Je toho překvapivě málo, protože jediné, co musíme udělat, je implementace metody length() s příjemcem Line. Tato implementace bude jednoduchá, protože metoda bude vracet délku úsečky, tj. vzdálenost mezi body [x1, y1] a [x2, y2]:

func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}

Již vytvořením této metody jsme dosáhli toho, že Line bude vyhovovat rozhraní OpenShape! Tuto skutečnost si jazyk Go ověří jak při překladu, tak i po spuštění aplikace.

Poznámka: povšimněte si, že skutečně nemusíme explicitně specifikovat (například klíčovým slovem implements apod.), že je rozhraní implementováno. To je poměrně velký sémantický rozdíl oproti programovacímu jazyku Java.

Korektní chování si otestujeme na tomto demonstračním příkladu:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length(shape OpenShape) float64 {
        return shape.length()
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}

        fmt.Println(line1)

        line_length := length(line1)
        fmt.Println(line_length)
}

Po spuštění tohoto příkladu dostaneme žádoucí výsledek:

{0 0 100 100}
141.4213562373095

Metody předepsané v rozhraní musí být implementovány zcela přesně, a to včetně návratového typu. Pokud typ nepatrně změníme (float32float64), nebude Line rozhraní OpenShape vyhovovat:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float32
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length(shape OpenShape) float32 {
        return shape.length()
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}

        fmt.Println(line1)

        line_length := length(line1)
        fmt.Println(line_length)
}

Pokus o překlad nyní skončí s chybou:

./07_B_wrong_return_type.go:37:23: cannot use line1 (type Line) as type OpenShape in argument to length:
        Line does not implement OpenShape (wrong type for length method)
                have length() float64
                want length() float32

8. Vícenásobná implementace stejného rozhraní

Nyní si ukážeme poněkud složitější příklad, který je odvozen od klasického „školního“ příkladu s hierarchií geometrických tvarů. Budeme mít deklarována dvě rozhraní, jedno pro otevřené křivky (nebo chcete-li tvary) a druhé pro křivky uzavřené:

type OpenShape interface {
        length() float64
}
 
type ClosedShape interface {
        area() float64
}

Dále vytvoříme dvě funkce, které budou akceptovat jakoukoli strukturu/záznam implementující dané rozhraní:

func length(shape OpenShape) float64 {
        return shape.length()
}
 
func area(shape ClosedShape) float64 {
        return shape.area()
}

Následně již můžeme bez problémů první rozhraní implementovat konkrétní strukturou představující úsečku. Následující řádky plně dostačují, aby struktura Line vyhovovala prvnímu rozhraní OpenShape:

type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}

Dále vytvoříme tři struktury, které vyhovují druhému rozhraní ClosedShape:

type Circle struct {
        x, y   float64
        radius float64
}
 
type Ellipse struct {
        x, y float64
        a, b float64
}
 
type Rectangle struct {
        x, y          float64
        width, height float64
}
 
func (rect Rectangle) area() float64 {
        return rect.width * rect.height
}
 
func (circle Circle) area() float64 {
        return math.Pi * circle.radius * circle.radius
}
 
func (ellipse Ellipse) area() float64 {
        return math.Pi * ellipse.a * ellipse.b
}

Vše si samozřejmě opět otestujeme:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type ClosedShape interface {
        area() float64
}
 
func length(shape OpenShape) float64 {
        return shape.length()
}
 
func area(shape ClosedShape) float64 {
        return shape.area()
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
type Circle struct {
        x, y   float64
        radius float64
}
 
type Ellipse struct {
        x, y float64
        a, b float64
}
 
type Rectangle struct {
        x, y          float64
        width, height float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func (rect Rectangle) area() float64 {
        return rect.width * rect.height
}
 
func (circle Circle) area() float64 {
        return math.Pi * circle.radius * circle.radius
}
 
func (ellipse Ellipse) area() float64 {
        return math.Pi * ellipse.a * ellipse.b
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
        fmt.Println("Line")
        fmt.Println(line1)
        fmt.Println(length(line1))
        fmt.Println(line1.length())
        fmt.Println()
 
        fmt.Println("Rectangle")
        r := Rectangle{x: 0, y: 0, width: 100, height: 100}
        fmt.Println(r)
        fmt.Println(area(r))
        fmt.Println(r.area())
        fmt.Println()
 
        fmt.Println("Circle")
        c := Circle{x: 0, y: 0, radius: 100}
        fmt.Println(c)
        fmt.Println(area(c))
        fmt.Println(c.area())
        fmt.Println()
 
        fmt.Println("Ellipse")
        e := Ellipse{x: 0, y: 0, a: 100, b: 50}
        fmt.Println(e)
        fmt.Println(area(e))
        fmt.Println(e.area())
        fmt.Println()
}

Povšimněte si, že můžeme volat jak funkci length a area, tak i přímo metody Line.length(), Circle.area() atd.:

Line
{0 0 100 100}
141.4213562373095
141.4213562373095
 
Rectangle
{0 0 100 100}
10000
10000
 
Circle
{0 0 100}
31415.926535897932
31415.926535897932
 
Ellipse
{0 0 100 50}
15707.963267948966
15707.963267948966

9. Řez se strukturami implementujícími společné rozhraní

Vzhledem k tomu, že se s využitím rozhraní může v programovacím jazyce Go realizovat polymorfní chování, asi nás nepřekvapí, že je povoleno vytvářet pole popř. řezy (rozdílných) struktur implementujících stejné rozhraní. Následující zápis je tak zcela korektní, protože všechny tři typy struktur (Rectangle, CircleEllipse) implementují rozhraní ClosedShape, což je překladačem samozřejmě kontrolováno:

shapes := []ClosedShape{
        Rectangle{x: 0, y: 0, width: 100, height: 100},
        Circle{x: 0, y: 0, radius: 100},
        Ellipse{x: 0, y: 0, a: 100, b: 50}}

V předchozím úryvku byl vytvořen řez se třemi strukturami, ovšem můžeme snadno vytvořit i pole:

shapes := [...]ClosedShape{
        Rectangle{x: 0, y: 0, width: 100, height: 100},
        Circle{x: 0, y: 0, radius: 100},
        Ellipse{x: 0, y: 0, a: 100, b: 50}}

Následně je možné iterovat přes všechny prvky pole či řezu a volat buď přímo implementované metody nebo funkce akceptující jako svůj parametr jakýkoli typ vyhovující rozhraní ClosedShape:

for _, shape := range shapes {
        fmt.Println(shape)
        fmt.Println(area(shape))
        fmt.Println(shape.area())
        fmt.Println()
}

Podívejme se nyní na konkrétní realizaci výše popsaného polymorfního chování:

package main
 
import (
        "fmt"
        "math"
)
 
type ClosedShape interface {
        area() float64
}
 
func area(shape ClosedShape) float64 {
        return shape.area()
}
 
type Circle struct {
        x, y   float64
        radius float64
}
 
type Ellipse struct {
        x, y float64
        a, b float64
}
 
type Rectangle struct {
        x, y          float64
        width, height float64
}
 
func (rect Rectangle) area() float64 {
        return rect.width * rect.height
}
 
func (circle Circle) area() float64 {
        return math.Pi * circle.radius * circle.radius
}
 
func (ellipse Ellipse) area() float64 {
        return math.Pi * ellipse.a * ellipse.b
}
 
func main() {
        shapes := []ClosedShape{
                Rectangle{x: 0, y: 0, width: 100, height: 100},
                Circle{x: 0, y: 0, radius: 100},
                Ellipse{x: 0, y: 0, a: 100, b: 50}}
 
        for _, shape := range shapes {
                fmt.Println(shape)
                fmt.Println(area(shape))
                fmt.Println(shape.area())
                fmt.Println()
        }
}

Po spuštění příkladu se vypíšou informace o třech strukturách – souřadnice, plocha a znovu plocha (pokaždé se ovšem volá jiná funkce resp. metoda):

{0 0 100 100}
10000
10000
 
{0 0 100}
31415.926535897932
31415.926535897932
 
{0 0 100 50}
15707.963267948966
15707.963267948966

10. Řezy rozhraní jako parametry funkce

Dynamické chování programovacího jazyka Go (alespoň v oblasti, v níž se používají rozhraní) jde samozřejmě ještě dále, protože můžeme deklarovat funkci akceptující řez rozhraním, tj. v praxi řez obsahující struktury vyhovující specifikovanému rozhraní. Taková funkce může vypadat například následovně:

func print_areas(shapes []ClosedShape) {
        for _, shape := range shapes {
                fmt.Println(shape)
                fmt.Println(area(shape))
                fmt.Println(shape.area())
                fmt.Println()
        }
}

Tuto funkci lze zavolat a přitom jí předat následující řez:

shapes := []ClosedShape{
        Rectangle{x: 0, y: 0, width: 100, height: 100},
        Circle{x: 0, y: 0, radius: 100},
        Ellipse{x: 0, y: 0, a: 100, b: 50}}
 
print_areas(shapes)

Opět se podívejme na ucelený demonstrační příklad, v němž se výše zmíněná funkce používá:

package main
 
import (
        "fmt"
        "math"
)
 
type ClosedShape interface {
        area() float64
}
 
func area(shape ClosedShape) float64 {
        return shape.area()
}
 
type Circle struct {
        x, y   float64
        radius float64
}
 
type Ellipse struct {
        x, y float64
        a, b float64
}
 
type Rectangle struct {
        x, y          float64
        width, height float64
}
 
func (rect Rectangle) area() float64 {
        return rect.width * rect.height
}
 
func (circle Circle) area() float64 {
        return math.Pi * circle.radius * circle.radius
}
 
func (ellipse Ellipse) area() float64 {
        return math.Pi * ellipse.a * ellipse.b
}
 
func print_areas(shapes []ClosedShape) {
        for _, shape := range shapes {
                fmt.Println(shape)
                fmt.Println(area(shape))
                fmt.Println(shape.area())
                fmt.Println()
        }
}
 
func main() {
        shapes := []ClosedShape{
                Rectangle{x: 0, y: 0, width: 100, height: 100},
                Circle{x: 0, y: 0, radius: 100},
                Ellipse{x: 0, y: 0, a: 100, b: 50}}

        print_areas(shapes)
}

Výsledek po spuštění tohoto příkladu bude shodný s výsledkem příkladu předchozího:

{0 0 100 100}
10000
10000
 
{0 0 100}
31415.926535897932
31415.926535897932
 
{0 0 100 50}
15707.963267948966
15707.963267948966

11. Datový typ implementující větší množství rozhraní

Datová struktura (záznam) samozřejmě může vyhovovat většímu množství rozhraní, což si můžeme snadno ukázat na struktuře pojmenované dosti obecně Type, která sice neobsahuje žádné položky, což však vůbec nevadí:

type Type struct{}

V programu je dále deklarována dvojice rozhraní:

type Interface1 interface {
        method1()
}
 
type Interface2 interface {
        method2()
}

Nyní postačuje implementovat obě metody, aby Type vyhovovala oběma rozhraním:

func (Type) method1() {
        fmt.Println("Type.method1")
}
 
func (Type) method2() {
        fmt.Println("Type.method2")
}

Podívejme se nyní na příklad, v němž jsou tato rozhraní deklarována a použita:

package main
 
import "fmt"
 
type Interface1 interface {
        method1()
}
 
type Interface2 interface {
        method2()
}
 
type Type struct{}
 
func (Type) method1() {
        fmt.Println("Type.method1")
}
 
func (Type) method2() {
        fmt.Println("Type.method2")
}
 
func f1(i Interface1) {
        fmt.Println("Interface1.f1")
        i.method1()
}
 
func f2(i Interface2) {
        fmt.Println("Interface2.f2")
        i.method2()
}
 
func main() {
        t := Type{}
 
        t.method1()
        t.method2()
        fmt.Println()
 
        f1(t)
        fmt.Println()
 
        f2(t)
        fmt.Println()
}

Povšimněte si, že můžeme bez problémů volat i funkce f1 a f2 a přitom jim předat strukturu typu t:

Type.method1
Type.method2
 
Interface1.f1
Type.method1
 
Interface2.f2
Type.method2

12. Skládání rozhraní

Další užitečnou vlastností typového systému programovacího jazyka Go je možnost při deklaraci nového rozhraní použít již existující rozhraní (této operaci se říká kompozice nebo též embedding, vložení). Díky tomu, že se v rozhraní nachází pouze deklarace hlaviček metod a nikoli jejich těla, nedojde při skládání k žádným nežádoucím a potenciálně nejednoznačným stavům (na rozdíl od vícenásobné dědičnosti). Podívejme se, jak lze rozhraní pojmenované Interface2 vytvořit a použít přitom již existující rozhraní se jménem Interface1:

package main
 
import "fmt"
 
type Interface1 interface {
        method1()
}
 
type Interface2 interface {
        Interface1
        method2()
}
 
type Type struct{}
 
func (Type) method1() {
        fmt.Println("Type.method1")
}
 
func (Type) method2() {
        fmt.Println("Type.method2")
}
 
func f1(i Interface1) {
        fmt.Println("Interface1.f1")
        i.method1()
}
 
func f2(i Interface2) {
        fmt.Println("Interface2.f2")
        i.method2()
}
 
func main() {
        t := Type{}
 
        t.method1()
        t.method2()
        fmt.Println()
 
        f1(t)
        fmt.Println()
 
        f2(t)
        fmt.Println()
}

Chování příkladu po jeho spuštění:

Type.method1
Type.method2
 
Interface1.f1
Type.method1
 
Interface2.f2
Type.method2

13. Gorutiny

Ve třetí části článku si ukážeme způsob vytvoření a zavolání takzvaných gorutin, o nichž jsme se zmínili v úvodní kapitole. Vytvoření gorutiny je ve skutečnosti velmi snadné. Ve chvíli, kdy například máme deklarovanou funkci message():

func message(id int) {
        fmt.Printf("gorutina %d\n", id)
}

Můžeme tuto funkci spustit přímo (synchronně):

message(1)

nebo nepřímo (asynchronně):

go message(1)

Jediným rozdílem je použití klíčového slova go.

V některých případech se setkáme s tím, že je implementace gorutiny uvedena přímo za klíčovým slovem go, což je samozřejmě možné, protože funkce jsou plnohodnotným datovým typem:

go func(msg string) {
    fmt.Println(msg)
}("Hello world")

14. Spuštění gorutiny

Gorutinu samozřejmě můžeme spustit přímo z hlavního vlákna aplikace:

package main
 
import "fmt"
 
func message(id int) {
        fmt.Printf("gorutina %d\n", id)
}
 
func main() {
        fmt.Println("main begin")
        go message(1)
        go message(2)
        fmt.Println("main end")
}

Po spuštění tohoto příkladu však s velkou pravděpodobností vůbec neuvidíme hlášení vypsané oběma gorutinami. Je tomu tak z toho důvodu, že hlavní vlákno s funkcí main skončí ještě předtím, než dojde k inicializaci a spuštění gorutin.

Jedno z řešení (ne příliš dobré!) spočívá v tom, že v hlavním vláknu počkáme na dokončení gorutin. Naše gorutiny jsou tak krátké a doslova primitivní, že plně postačuje počkat „pouze“ dvě sekundy:

package main
 
import (
        "fmt"
        "time"
)
 
func message(id int) {
        fmt.Printf("gorutina %d\n", id)
}
 
func main() {
        fmt.Println("main begin")
        go message(1)
        go message(2)
        time.Sleep(2 * time.Second)
        fmt.Println("main end")
}

Výsledek po spuštění:

main begin
gorutina 1
gorutina 2
main end

15. Spuštění většího množství gorutin na pozadí

Můžeme si samozřejmě vyzkoušet složitější příklady, například trojici gorutin, které zapisují řetězec na jediný řádek s různým zpožděním:

package main
 
import (
        "fmt"
        "time"
)
 
func print_chars() {
        for ch := 'a'; ch <= 'z'; ch++ {
                fmt.Printf("%c", ch)
                time.Sleep(200 * time.Millisecond)
        }
}
 
func print_dots() {
        for i := 0; i < 30; i++ {
                fmt.Print(".")
                time.Sleep(200 * time.Millisecond)
        }
}
 
func print_spaces() {
        for i := 0; i < 60; i++ {
                fmt.Print(" ")
                time.Sleep(110 * time.Millisecond)
        }
}
 
func main() {
        fmt.Println("main begin")
        go print_chars()
        go print_spaces()
        go print_dots()
        time.Sleep(6 * time.Second)
        fmt.Println("main end")
}

Výsledek může (ale nemusí) vypadat následovně:

main begin
a.  b.  .c  .d  e.  .f g.  .h  i.  j.  .k l.  .m  n.  o.  p.  .q .r  .s  t.  u.  .v .w  .x  .y  z.  .  . .  .  main end

Gorutiny lze volat z jiných gorutin:

package main
 
import (
        "fmt"
        "time"
)
 
func print_chars() {
        for ch := 'a'; ch <= 'z'; ch++ {
                fmt.Printf("%c", ch)
                time.Sleep(200 * time.Millisecond)
        }
}
 
func print_dots() {
        for i := 0; i < 30; i++ {
                fmt.Print(".")
                time.Sleep(200 * time.Millisecond)
        }
}
 
func print_spaces() {
        go print_chars()
        go print_dots()
        for i := 0; i < 60; i++ {
                fmt.Print(" ")
                time.Sleep(110 * time.Millisecond)
        }
}
 
func main() {
        fmt.Println("main begin")
        go print_spaces()
        time.Sleep(6 * time.Second)
        fmt.Println("main end")
}

16. Kanály – technologie pro komunikaci mezi gorutinami

Knihovny či nové jazykové konstrukce umožňující používání kanálů (či front) pro asynchronní komunikaci mezi různými částmi vyvíjených aplikací, se v posledních několika letech těší poměrně velké popularitě. Ta je způsobena dvěma faktory. První důvod spočívá ve snaze o zjednodušení návrhu (či porozumění) vyvíjené aplikace, zejména ve chvíli, kdy se v rámci jednoho programu předávají data (resp. objekty) mezi částmi, jejichž funkce může být dobře izolována od částí ostatních.

Druhý důvod je poněkud prozaičtější – v některých situacích je nutné dosáhnout zvýšení efektivity aplikace (například zvýšit počet odpovědí, které může server vygenerovat za určitou časovou jednotku) a přitom není možné či vhodné využívat řešení založené na použití většího množství vláken spravovaných přímo operačním systémem. Naprosto typickým příkladem jsou virtuální stroje JavaScriptu, které povětšinou umožňují běh aplikace v jediném vláknu (což je ovšem s ohledem na „kvalitu“ některých programových kódů spíše výhodou…).

Některé programovací jazyky, zejména pak v tomto seriálu popisovaný jazyk Go, obsahují prostředky sloužící pro zajištění asynchronní komunikace přímo v syntaxi (a samozřejmě též v sémantice) jazyka. Konkrétně v případě jazyka Go se jedná o gorutiny představené v předchozích kapitolách, které jsou doplněny o specializované operace sloužící pro zápis či čtení dat z kanálů. Tyto specializované operace jsou představovány operátorem <- (ten má dva významy v závislosti na tom, zda je před operátorem uveden identifikátor představující kanál či nikoli).

Typickým příkladem, v němž se použije kanál, je komunikace mezi producentem a konzumentem:

+-----------+                            +-----------+
| producent |          +-----+           | konzument |
|           |... > ... |kanál} ... > ... |           |
| go block  |          +-----+           | go block  |
+-----------+                            +-----------+

Kanál může být v případě potřeby využíván více producenty (i konzumenty), takže se původní schéma komunikace může změnit:

+-----------+
| producent |
|     #1    |... >.........
| go block  |             :
+-----------+             :
                          :
+-----------+             :              +-----------+
| producent |          +-----+           | konzument |
|     #2    |... > ... |kanál} ... < ... |           |
| go block  |          +-----+           | go block  |
+-----------+             :              +-----------+
                          :
+-----------+             :
| producent |             :
|     #3    |... >........:
| go block  |
+-----------+

Ukažme si nyní velmi jednoduchý příklad použití kanálu při čekání na dokončení gorutiny. Jedná se o mnohem čistší řešení problému, s nímž jsme se setkali v předchozích kapitolách. Nyní je gorutině předán kanál (s kapacitou jediného čísla typu int) a kód ve funkci main nejprve gorutinu zavolá a posléze čeká na hodnotu zapsanou do kanálu. Toto čekání je blokující – hlavní blok tedy skutečně bude v daném místě čekat na operaci zápisu do kanálu. Kromě vlastní hodnoty se při čtení z kanálu získá i jeho stav, tj. zda je kanál stále otevřen a připraven pro komunikaci:

package main
 
import "fmt"
 
func message(id int, channel chan int) {
        fmt.Printf("gorutina %d\n", id)
 
        // zápis libovolné hodnoty do kanálu
        channel <- 1
}
 
func main() {
        channel := make(chan int)
 
        fmt.Println("main begin")
        go message(1, channel)
 
        fmt.Println("waiting...")
 
        // blokující čtení z kanálu
        code, status := <-channel
 
        fmt.Printf("received code: %d and status: %t\n", code, status)
        fmt.Println("main end")
}

17. Implementace workerů s využitím gorutin a kanálů

Prozatím bez podrobnějšího popisu si ukažme, jak lze implementovat jednoduchý systém s workery s využitím gorutin a kanálů. Workerům se úlohy předávají přes kanál task_channel, druhý kanál slouží pro jejich ukončení:

package main
 
import "fmt"
 
func worker(task_channel chan int, worker_done chan bool) {
        fmt.Println("worker started")
        for {
                value, more := <-task_channel
                if more {
                        fmt.Printf("worker received task with parameter %d\n", value)
                } else {
                        fmt.Println("finishing worker")
                        worker_done <- true
                        fmt.Println("worker finished")
                        return
                }
        }
}
 
func main() {
        task_channel := make(chan int)
        worker_done := make(chan bool)
 
        fmt.Println("main begin")
 
        go worker(task_channel, worker_done)
 
        for i := 1; i <= 10; i++ {
                fmt.Printf("sending task with parameter %d\n", i)
                task_channel <- i
        }
        //close(task_channel)
 
        fmt.Println("waiting for workers...")
 
        code, status := <-worker_done
 
        fmt.Printf("received code: %t and status: %t\n", code, status)
        fmt.Println("main end")
}

Práce jednoho workera:

main begin
sending task with parameter 1
worker started
worker received task with parameter 1
sending task with parameter 2
sending task with parameter 3
worker received task with parameter 2
worker received task with parameter 3
sending task with parameter 4
sending task with parameter 5
worker received task with parameter 4
worker received task with parameter 5
sending task with parameter 6
sending task with parameter 7
worker received task with parameter 6
worker received task with parameter 7
sending task with parameter 8
sending task with parameter 9
worker received task with parameter 8
worker received task with parameter 9
sending task with parameter 10
waiting for workers...
worker received task with parameter 10
finishing worker
worker finished
received code: true and status: true
main end

Workerů můžeme spustit více, zde konkrétně tři:

package main
 
import (
        "fmt"
        "time"
)
 
func worker(id int, task_channel chan int, worker_done chan bool) {
        fmt.Printf("worker %d started\n", id)
        for {
                value, more := <-task_channel
                if more {
                        fmt.Printf("worker %d received task with parameter %d\n", id, value)
                        time.Sleep(2 * time.Second)
                } else {
                        fmt.Printf("finishing worker %d\n", id)
                        worker_done <- true
                        fmt.Printf("worker %d finished\n", id)
                        return
                }
        }
}
 
func main() {
        task_channel := make(chan int)
        worker_done := make(chan bool)
 
        fmt.Println("main begin")
 
        for i := 1; i <= 3; i++ {
                go worker(i, task_channel, worker_done)
        }
        time.Sleep(2 * time.Second)
 
        for i := 1; i <= 10; i++ {
                fmt.Printf("sending task with parameter %d\n", i)
                task_channel <- i
        }
        close(task_channel)
 
        fmt.Println("waiting for workers...")
 
        code, status := <-worker_done
 
        fmt.Printf("received code: %t and status: %t\n", code, status)
        fmt.Println("main end")
}

Nyní se workeři o práci poměrně spravedlivě dělí:

main begin
worker 1 started
worker 2 started
worker 3 started
sending task with parameter 1
sending task with parameter 2
sending task with parameter 3
sending task with parameter 4
worker 2 received task with parameter 2
worker 1 received task with parameter 1
worker 3 received task with parameter 3
worker 3 received task with parameter 4
sending task with parameter 5
sending task with parameter 6
sending task with parameter 7
worker 2 received task with parameter 5
worker 1 received task with parameter 6
worker 3 received task with parameter 7
sending task with parameter 8
worker 1 received task with parameter 8
sending task with parameter 9
sending task with parameter 10
worker 2 received task with parameter 9
worker 3 received task with parameter 10
waiting for workers...
finishing worker 1
worker 1 finished
received code: true and status: true
main end

18. Deadlock a jeho detekce v runtime

Kanály, které jsme až doposud pro komunikaci mezi gorutinami používali, mohly obsahovat pouze jediný prvek, takže se jejich chování dá shrnout přibližně takto:

  • Zápis do prázdného kanálu je neblokující operace (kanál se zápisem zaplní, další zápis již tedy bude blokující, pokud mezitím nedojde ke čtení).
  • Zápis do neprázdného kanálu je blokující operace.
  • Čtení z prázdného kanálu je blokující operace (počká se na další zápis).
  • Čtení z neprázdného kanálu je neblokující operace (kanál se vyprázdní).

V praxi však může dojít i k problémové situaci, a to ve chvíli, kdy použijeme větší množství kanálů, přičemž dvě gorutiny budou navzájem čekat na zápis provedený do různých kanálů. V takovém případě dojde k takzvanému deadlocku, což si můžeme relativně snadno otestovat:

bitcoin_skoleni

package main
 
import "fmt"
 
func worker(task_channel chan int, worker_done chan bool) {
        fmt.Println("worker started")
        for {
                value, more := <-task_channel
                if more {
                        fmt.Printf("worker received task with parameter %d\n", value)
                } else {
                        fmt.Println("finishing worker")
                        worker_done <- true
                        fmt.Println("worker finished")
                        return
                }
        }
}
 
func main() {
        task_channel := make(chan int)
        worker_done := make(chan bool)
 
        fmt.Println("main begin")
 
        go worker(task_channel, worker_done)
 
        for i := 1; i <= 10; i++ {
                fmt.Printf("sending task with parameter %d\n", i)
                task_channel <- i
        }
        // !!!
        // close(task_channel)
        // !!!
 
        fmt.Println("waiting for workers...")
 
        code, status := <-worker_done
 
        fmt.Printf("received code: %t and status: %t\n", code, status)
        fmt.Println("main end")
}

Kanál task_channel není uzavřen, tudíž workeři nikdy neskončí a budou čekat na data, která nedostanou:

main begin
sending task with parameter 1
worker started
worker received task with parameter 1
sending task with parameter 2
sending task with parameter 3
worker received task with parameter 2
worker received task with parameter 3
sending task with parameter 4
sending task with parameter 5
worker received task with parameter 4
worker received task with parameter 5
sending task with parameter 6
sending task with parameter 7
worker received task with parameter 6
worker received task with parameter 7
sending task with parameter 8
sending task with parameter 9
worker received task with parameter 8
worker received task with parameter 9
sending task with parameter 10
waiting for workers...
worker received task with parameter 10
fatal error: all goroutines are asleep - deadlock!
 
goroutine 1 [chan receive]:
main.main()
        /home/tester/temp/out/go-root/article_04/20_workers_deadlock.go:45 +0x1f1
 
goroutine 5 [chan receive]:
main.worker(0xc00005e060, 0xc00005e0c0)
        /home/tester/temp/out/go-root/article_04/20_workers_deadlock.go:15 +0xf2
created by main.main
        /home/tester/temp/out/go-root/article_04/20_workers_deadlock.go:33 +0xed
exit status 2
Poznámka: povšimněte si, že deadlock byl v runtime detekován, což je velmi užitečné.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Popis Cesta
1 01_methods.go deklarace metody pro záznam/strukturu https://github.com/tisnik/go-fedora/blob/master/article04/01_met­hods.go
2 02_methods_with_parameters.go deklarace metody s parametry https://github.com/tisnik/go-fedora/blob/master/article04/02_met­hods_with_parameters.go
3 03_methods_ptr.go předání ukazatele na strukturu do metody https://github.com/tisnik/go-fedora/blob/master/article04/03_met­hods_ptr.go
4 04_methods_ptr_better_solution.go vylepšení předchozího příkladu https://github.com/tisnik/go-fedora/blob/master/article04/04_met­hods_ptr_better_solution.go
5 05_interface.go deklarace trojice rozhraní https://github.com/tisnik/go-fedora/blob/master/article04/05_in­terface.go
6 06_interface_implementation.go rozhraní, které má být implementováno https://github.com/tisnik/go-fedora/blob/master/article04/06_in­terface_implementation.go
7 07_interface_implementati­on_with_method.go implementace rozhraní https://github.com/tisnik/go-fedora/blob/master/article04/07_in­terface_implementation_wit­h_method.go
8 08_more_implementations.go více rozhraní https://github.com/tisnik/go-fedora/blob/master/article04/08_mo­re_implementations.go
9 09_slice_of_interfaces.go řez s rozhraními https://github.com/tisnik/go-fedora/blob/master/article04/09_sli­ce_of_interfaces.go
10 10_slice_of_interfaces_as_param.go řez s rozhraními https://github.com/tisnik/go-fedora/blob/master/article04/10_sli­ce_of_interfaces_as_param­.go
11 11_multiple_interfaces.go implementace více rozhraní https://github.com/tisnik/go-fedora/blob/master/article04/11_mul­tiple_interfaces.go
12 12_embedded_interface.go vložená rozhraní https://github.com/tisnik/go-fedora/blob/master/article04/12_em­bedded_interface.go
13 13_simple_goroutine.go jednoduchá gorutina https://github.com/tisnik/go-fedora/blob/master/article04/13_sim­ple_goroutine.go
14 14_wait_for_goroutine.go čekání na dokončení gorutiny https://github.com/tisnik/go-fedora/blob/master/article04/14_wa­it_for_goroutine.go
15 15_multiple_goroutines.go spuštění většího množství gorutin https://github.com/tisnik/go-fedora/blob/master/article04/15_mul­tiple_goroutines.go
16 16_goroutine_from_goroutine.go volání gorutiny z jiné gorutiny https://github.com/tisnik/go-fedora/blob/master/article04/16_go­routine_from_goroutine.go
17 17_channel.go vytvoření a použití kanálu https://github.com/tisnik/go-fedora/blob/master/article04/17_chan­nel.go
18 18_worker.go implementace workera https://github.com/tisnik/go-fedora/blob/master/article04/18_wor­ker.go
19 19_more_workers.go větší množství workerů https://github.com/tisnik/go-fedora/blob/master/article04/19_mo­re_workers.go
20 20_workers_deadlock.go vznik deadlocku https://github.com/tisnik/go-fedora/blob/master/article04/20_wor­kers_deadlock.go

20. Odkazy na Internetu

  1. The Go Programming Language (home page)
    https://golang.org/
  2. GoDoc
    https://godoc.org/
  3. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  4. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  5. The Go Programming Language Specification
    https://golang.org/ref/spec
  6. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  7. Package builtin
    https://golang.org/pkg/builtin/
  8. Package fmt
    https://golang.org/pkg/fmt/
  9. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  10. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  11. Learning Go
    https://www.miek.nl/go/
  12. Go Bootcamp
    http://www.golangbootcamp.com/
  13. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  14. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  15. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  16. The Go Blog
    https://blog.golang.org/
  17. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  18. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  19. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  20. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  21. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  22. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  23. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  24. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  25. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  26. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  27. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  28. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  29. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  30. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  31. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  32. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  33. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  34. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  35. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  36. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  37. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  38. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  39. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  40. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  41. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  42. Go vs. Python
    https://www.peterbe.com/plog/govspy
  43. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  44. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  45. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  46. Go by Example: Slices
    https://gobyexample.com/slices
  47. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  48. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  49. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  50. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  51. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  52. nils In Go
    https://go101.org/article/nil.html
  53. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  54. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  55. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  56. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  57. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  58. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  59. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  60. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  61. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  62. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  63. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  64. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  65. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  66. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  67. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  68. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  69. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  70. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  71. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  72. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  73. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  74. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  75. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  76. Selectors
    https://golang.org/ref/spec#Selectors
  77. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  78. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  79. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  80. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  81. Part 21: Goroutines
    https://golangbot.com/goroutines/
  82. Part 22: Channels
    https://golangbot.com/channels/
  83. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  84. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  85. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.