Hlavní navigace

Knihovny pro Go umožňující naplánování a spouštění periodických úloh

Pavel Tišnovský

Mezi další užitečné knihovny jazyka Go patří ty zajišťující periodické spouštění úloh. Jedná se vlastně o reimplementaci chování nástroje cron, ovšem všechny plánované úlohy jsou spouštěny v jediném procesu, každá v samostatné gorutině.

Doba čtení: 36 minut

Sdílet

11. Naplánování většího množství úloh

12. Použití dalších metod pro naplánování úloh

13. Balíček clockwerk

14. Naplánování jedné úlohy

15. Naplánování několika nezávislých úloh

16. Použití datového typu Duration při plánování úloh

17. Úprava příkladu s více úlohami

18. Vylepšení plánování asynchronních úloh s balíčkem JobRunner

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

20. Odkazy na Internetu

1. Knihovny pro Go umožňující naplánování a spouštění periodických úloh

Ve dvacáté první části seriálu o programovacím jazyku Go se budeme zabývat knihovnami (resp. přesněji řečeno balíčky), které slouží pro naplánování úloh, jež se mají periodicky spouštět v určitém časovém intervalu. Příkladem může být úloha, která každou minutu zjišťuje, zda je nějaká služba či databáze dostupná („živá“), popř. jiná úloha, která každý pátek v čase 23:59 zajistí vyčištění databáze (vacuuming, viz též [1] [2]). V unixových systémech se pro periodické spouštění různých úloh používá nástroj nazvaný cron, který běží jako služba (resp. démon) a je řízen tabulkami s pravidly uloženými v souborech, které se typicky nalézají v adresáři /var/spool/cron/crontabs/. Bližší informace o nástroji cron lze získat přímo z jeho manuálové stránky, kterou s poměrně velkou pravděpodobností máte nainstalovánu i ve svém systému:

$ man cron

Samotný popis tabulek s definicí periodicky spouštěných úloh ovšem naleznete v jiné manuálové stránce, a to v páté sekci:

$ man 5 crontab
Poznámka: v tomto případě je nutné číslo sekce uvést, jinak se zobrazí sice stejně pojmenovaná manuálová stránka, ovšem z první sekce, která popisuje jiný typ souborů.

Příklad tabulky nástroje cron:

# Minute   Hour   Day of Month       Month          Day of Week        Command
# (0-59)  (0-23)     (1-31)    (1-12 or Jan-Dec)  (0-6 or Sun-Sat)
    0        2          12             *                *            /usr/bin/find
    0       23       15-21             *                1            /usr/something-else foobar

Ovšem i přes velkou užitečnost nástroje cron se nemusí ve všech případech jednat o to nejlepší možné řešení. Někdy je totiž nutné úlohy plánovat dynamicky na základě různých podmínek, samotný zápis pravidel v crontabs je někdy komplikovaný a v neposlední řadě nemusí být spouštění úloh v určitém čase tak triviální, jak by se mohl na první pohled zdát – mnohdy je totiž nutné zjistit, jak dopadla předchozí úloha, zda je již ukončena atd. To lze řešit použitím různých lock filů (souborů fungujících jako zámky), ovšem se všemi nevýhodami, které toto řešení přináší (zajištění stavu lock filů, pokud proces s úlohou havaruje apod.). Některé vlastnosti nástroje cron a návrhy na vylepšení jsou shrnuty v článku Rethinking Cron.

Samozřejmě, že k nástroji cron existují i různé více či méně povedené alternativy, které jsou většinou realizovány nějakou knihovnou. Samotné naplánované úlohy jsou pak typicky reprezentovány funkcí, která se (asynchronně) spustí v určitém časovém intervalu, například každou sekundu, poslední pátek v měsíci, v sedm hodin ráno každý pracovní den apod. Mnohé z těchto knihoven jsou inspirovány balíčkem whenever určeným pro programovací jazyk Ruby. Zajímavá je například knihovna schedule pro Python, ovšem dnes se budeme zabývat knihovnami, resp. balíčky, které jsou určeny pro programovací jazyk Go. Již na tomto místě je nutné říci, že implementace takových knihoven v Go bývá dosti zjednodušena, a to díky existenci gorutin, kanálů a v neposlední řadě taktéž datového typu Duration ze standardního balíčku time, který mnohé tyto knihovny používají.

2. Datový typ Duration ze standardního balíčku fmt

Jak jsme si již řekli v úvodní kapitole, používá se u některých dále popsaných knihoven pro plánování periodických úloh standardní balíček time a jeho datový typ Duration určený pro reprezentaci časového intervalu, což může být jak doba trvání nějaké události, tak i určení nějakého časového intervalu. Samotné hodnoty typu Duration jsou interně reprezentovány celým 64bitovým číslem s přesností nanosekund a rozsahem přibližně 290 let:

type Duration int64
Poznámka: připomeňme si, že v programovacím jazyku Go je nutné provádět explicitní přetypování, což v našem případě znamená, že interně je sice typ Duration ekvivalentní typu int64, ovšem tato interní ekvivalence v praxi vůbec neznamená, že lze jakoukoli hodnotu typu int64 použít v dále popsaných metodách ve funkci příjemce!

Důležité je také znát funkce, které jako svoji výstupní hodnotu vrací hodnotu typu Duration (a popř. i chybu error ve druhé návratové hodnotě). Tyto funkce umožňují pracovat s časovým intervalem zapsaným v lidsky dobře čitelném formátu (formou řetězce):

Funkce Stručný popis
ParseDuration(s string) (Duration, error) zpracování vstupního řetězce s lidsky zapsaným časovým intervalem a převod na typ Duration
Since(t Time) Duration vrátí čas, který uplynul od okamžiku t
Until(t Time) Duration vrátí čas, který musí uplynout do okamžiku t

K datovému typu Duration je vztaženo i několik užitečných metod:

Metoda Stručný popis
(d Duration) Hours() float64 převod časového intervalu na hodiny (může se jednat o desetinné číslo)
(d Duration) Minutes() float64 převod časového intervalu na minuty (opět se může jednat o desetinné číslo)
(d Duration) Seconds() float64 převod časového intervalu na sekundy (může se jednat o desetinné číslo)
(d Duration) Nanoseconds() int64 převod časového intervalu na celý počet nanosekund
   
(d Duration) String() string převod časového intervalu na řetězec ve formátu „50h30m2.5s“, vždy se začíná nenulovým číslem, mění se pouze jednotky
   
(d Duration) Round(m Duration) Duration zaokrouhlení časového intervalu na nejbližší celé násobky intevalu m
(d Duration) Truncate(m Duration) Duration zaokrouhlení časového intervalu směrem k nule

Zajímavá je především metoda String vracející řetězcovou podobu intervalu. Prakticky vždy, s výjimkou intervalu s nulovou délkou, vrací tato metoda řetězec, který začíná nenulovou cifrou, například již zmíněných „50h30m“, nebo „30m“ popř. „30m10s“.

Převody na hodiny, minuty a sekundy vrací hodnotu typu float64, protože se obecně jedná o desetinná čísla. Výjimkou je převod na nanosekundy, protože v tomto případě se vrací přímo nijak neupravený počet nanosekund, který je interní reprezentací časového intervalu.

3. Ukázky použití funkcí a metod datového typu Duration

Funkce a metody vztažené k datovému typu Duration a popsané v předchozí kapitole si samozřejmě můžeme velmi snadno otestovat; vystačíme si přitom pouze se standardními balíčky programovacího jazyka Go (prozatím tedy není nutné instalovat další knihovny).

Začneme převodem řetězce „1h“ odpovídajícího přesně jedné hodině na typ Duration a získání informací o tomto intervalu. Tento algoritmus je implementován v dnešním prvním demonstračním příkladu:

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        d, _ := time.ParseDuration("1h")
 
        fmt.Println(d.String())
        fmt.Printf("Hours:   %2.0f\n", d.Hours())
        fmt.Printf("Minutes: %2.0f\n", d.Minutes())
}

S výsledky:

1h0m0s
Hours:    1
Minutes: 60

Ve druhém příkladu použijeme odlišný časový interval odpovídající jedné hodině, šesti minutám a deseti sekundám:

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        d, _ := time.ParseDuration("1h6m10s")
 
        fmt.Println(d.String())
        fmt.Printf("Hours:   %4.2f\n", d.Hours())
        fmt.Printf("Minutes: %2.0f\n", d.Minutes())
        fmt.Printf("Seconds: %2.0f\n", d.Seconds())
        fmt.Printf("ns:      %d\n", d.Nanoseconds())
}

S výsledkem, včetně převodu na sekundy a celé nanosekundy:

1h6m10s
Hours:   1.10
Minutes: 66
Seconds: 3970
ns:      3970000000000

Samozřejmě je možné specifikovat i mnohem kratší časové intervaly, například:

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        d, _ := time.ParseDuration("200ms")
 
        fmt.Println(d.String())
        fmt.Printf("Seconds: %4.2f\n", d.Seconds())
        fmt.Printf("ns:      %d\n", d.Nanoseconds())
}

S výsledkem:

200ms
Seconds: 0.20
ns:      200000000

Nic nám nebrání použít dokonce intervaly nanosekundové a mikrosekundové, což si ukážeme v dalším příkladu:

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        d, _ := time.ParseDuration("0.1µs1ns")
 
        fmt.Println(d.String())
 
        fmt.Printf("ns:      %d\n", d.Nanoseconds())
}

S výsledkem:

101ns
ns:      101
Poznámka: pro zápis mikrosekundy lze použít buď „us“ nebo „µs“, protože – jak již víme – programovací jazyk Go plně podporuje Unicode.

Na závěr se ještě podívejme, jakým způsobem je možné provést zaokrouhlení nějakého časového intervalu takovým způsobem, aby byl nový interval celočíselným násobkem intervalu jiného. V pátém demonstračním příkladu je ukázáno zaokrouhlení intervalu o délce trvání 3 hodiny 15 minut na celé hodiny:

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        d, _ := time.ParseDuration("3h15m")
        e, _ := time.ParseDuration("1h")
        f := d.Round(e)
 
        fmt.Println(f.String())
        fmt.Printf("Hours:   %2.0f\n", f.Hours())
        fmt.Printf("Minutes: %2.0f\n", f.Minutes())
        fmt.Printf("Seconds: %4.0f\n", f.Seconds())
        fmt.Printf("ns:      %d\n", f.Nanoseconds())
}

S očekávatelnými výsledky:

3h0m0s
Hours:    3
Minutes: 180
Seconds: 10800
ns:      10800000000000

Podobně můžeme interval zaokrouhlit na celé čtvrthodiny:

package main
 
import (
        "fmt"
        "time"
)
 
func main() {
        d, _ := time.ParseDuration("3h25m")
        e, _ := time.ParseDuration("15m")
        f := d.Round(e)
 
        fmt.Println(f.String())
        fmt.Printf("Hours:   %4.2f\n", f.Hours())
        fmt.Printf("Minutes: %2.0f\n", f.Minutes())
        fmt.Printf("Seconds: %4.0f\n", f.Seconds())
        fmt.Printf("ns:      %d\n", f.Nanoseconds())
}

S výsledky:

3h30m0s
Hours:   3.50
Minutes: 210
Seconds: 12600
ns:      12600000000000

4. Balíček go-cron

První knihovnou určenou pro plánování periodicky se opakujících úloh v Go s níž se v dnešním článku seznámíme, bude knihovna nazvaná jednoduše a přímočaře go-cron. Tato knihovna, kterou naleznete na adrese https://github.com/rk/go-cron, umožňuje naplánování úloh, přičemž každá úloha je realizována běžnou funkcí, která je v určitý naplánovaný časový okamžik spuštěna. Žádné další možnosti tato knihovna nenabízí, což je možné v tomto kontextu považovat za výhodu (složitější knihovna bude popsána v závěru tohoto článku, ovšem mnohdy si vystačíme právě s možnostmi nabízenými knihovnou go-cron).

Instalace knihovny go-cron proběhne s využitím standardního nástroje go get, a to konkrétně následujícím způsobem:

$ go get github.com/rk/go-cron

Tato knihovna programátorům nabízí několik funkcí pro naplánování periodické úlohy. Tyto funkce jsou zmíněny v následující tabulce:

Funkce Parametry Stručný popis
NewDailyJob hour, minute, second int8, task func(time.Time) spuštění úlohy, která se má opakovat každý den v určitý čas
NewWeeklyJob weekday, hour, minute, second int8, task func(time.Time) spuštění úlohy, která se má opakovat v daný den v týdnu v určitý čas
NewMonthlyJob day, hour, minute, second int8, task func(time.Time) spuštění úlohy, která se má opakovat v zadaný den v měsíci
     
NewCronJob month, day, weekday, hour, minute, second int8, task func(time.Time) naplánování obecné periodicky se opakující úlohy
Poznámka: existuje speciální hodnota –1, kterou můžete ve výše zmíněných funkcích použít ve chvíli, kdy se nemá daná časová jednotka brát v úvahu (-1 tedy znamená „libovolná hodnota této jednotky“). Ukázky si uvedeme v navazujících kapitolách.

5. Základní použití knihovny go-cron pro naplánování úloh

Pro první seznámení se s možnostmi knihovny go-cron slouží následující demonstrační příklad, v němž je naplánována jediná periodicky se opakující úloha implementovaná samostatnou funkcí nazvanou task:

func task(t time.Time) {
        println(t.String())
}

Naplánování periodické úlohy proběhne tímto způsobem:

cron.NewDailyJob(-1, -1, -1, task)
Poznámka: jak již víme z předchozí kapitoly, hodnoty –1 znamenají, že pro danou časovou jednotku neplatí žádné omezení.

V úplném výpisu zdrojového kódu demonstračního příkladu si povšimněte, že je nutné explicitně zajistit, aby gorutina, v níž je spuštěna funkce main, neskončila, protože by to automaticky znamenalo i ukončení celé aplikace, pochopitelně se všemi naplánovanými úlohami. Jedno z řešení představuje použití kanálu, z něhož se na konci funkce main pokusíme přečíst data, i když se ve skutečnosti do kanálu žádná data nikde nezapíšou. Jedná se o jasně blokující operaci, která zajistí, že k ukončení funkce main nedojde (program budeme muset ukončit jinak, buď přímo z terminálu, nebo pomocí příkazu kill):

package main
 
import (
        "github.com/rk/go-cron"
        "time"
)
 
func task(t time.Time) {
        println(t.String())
}
 
func main() {
        c := make(chan bool)
 
        cron.NewDailyJob(-1, -1, -1, task)
 
        <-c
}

Po spuštění tohoto příkladu by se na terminálu postupně měly objevovat informace o spuštěné úloze. Vzhledem k tomu, že je funkci (představující úlohu) automaticky předáno i časové razítko spuštění, můžeme snadno ověřit přibližnou přesnost plánování periodicky se opakujících úloh:

2019-04-11 20:39:20.983537518 +0200 CEST m=+0.000208862
2019-04-11 20:39:21.983697 +0200 CEST m=+1.000368303
2019-04-11 20:39:22.983883447 +0200 CEST m=+2.000554827
2019-04-11 20:39:23.984028499 +0200 CEST m=+3.000699871
2019-04-11 20:39:24.984184861 +0200 CEST m=+4.000856176
2019-04-11 20:39:25.984334132 +0200 CEST m=+5.001005468
2019-04-11 20:39:26.984449806 +0200 CEST m=+6.001121161
2019-04-11 20:39:27.984610121 +0200 CEST m=+7.001281438
2019-04-11 20:39:28.984760969 +0200 CEST m=+8.001432300
...
...
...

Příklad je pro lepší čitelnost vhodné upravit takovým způsobem, aby se namísto celočíselných konstant -1 používaly symbolické konstanty ANY. Úprava příkladu tímto způsobem je triviální, což je ostatně patrné i při pohledu na jeho zdrojový kód:

package main
 
import (
        "github.com/rk/go-cron"
        "time"
)
 
func task(t time.Time) {
        println(t.String())
}
 
func main() {
        c := make(chan bool)
 
        cron.NewDailyJob(cron.ANY, cron.ANY, cron.ANY, task)
 
        <-c
}

Výsledky běhu tohoto příkladu:

2019-04-11 20:41:00.025596852 +0200 CEST m=+34.005213481
2019-04-11 20:42:00.034019284 +0200 CEST m=+94.013635937
2019-04-11 20:43:00.042509678 +0200 CEST m=+154.022126308
...
...
...

6. Naplánování většího množství úloh v jediném procesu

Nic nám samozřejmě nebrání naplánovat si v jednom procesu (aplikaci) větší množství úloh. V dalším demonstračním příkladu jsou deklarovány tři funkce, z nichž každá obsahuje implementaci jedné úlohy:

func task1(t time.Time) {
        println("task1:", t.String())
}
 
func task2(t time.Time) {
        println("task2:", t.String())
}
 
func task3(t time.Time) {
        println("task3:", t.String())
}

Každá z těchto funkcí se bude spouštět s jinou periodou – každou celou minutu, každou desátou sekundu v minutě popř. každou sekundu:

cron.NewDailyJob(cron.ANY, cron.ANY, 0, task1)
cron.NewDailyJob(cron.ANY, cron.ANY, 10, task2)
cron.NewDailyJob(cron.ANY, cron.ANY, cron.ANY, task3)

Úplný zdrojový kód tohoto příkladu vypadá následovně:

package main
 
import (
        "github.com/rk/go-cron"
        "time"
)
 
func task1(t time.Time) {
        println("task1:", t.String())
}
 
func task2(t time.Time) {
        println("task2:", t.String())
}
 
func task3(t time.Time) {
        println("task3:", t.String())
}
 
func main() {
        c := make(chan bool)
 
        cron.NewDailyJob(cron.ANY, cron.ANY, 0, task1)
        cron.NewDailyJob(cron.ANY, cron.ANY, 10, task2)
        cron.NewDailyJob(cron.ANY, cron.ANY, cron.ANY, task3)
 
        <-c
}

Podívejme se nyní, jak se bude příklad chovat po spuštění. Tučně jsou zvýrazněna volání první a druhé úlohy – povšimněte si, že se skutečně spustily v celou minutu, popř. v desáté sekundě minuty (čas spuštění není pochopitelně zcela přesný a závisí mj. i na tom, kdy přesně byl program spuštěn):

task3: 2019-04-15 13:20:59.365354556 +0200 CEST m=+40.007242742
task3: 2019-04-15 13:21:00.365510079 +0200 CEST m=+41.007398258
task1: 2019-04-15 13:21:00.365510079 +0200 CEST m=+41.007398258
task3: 2019-04-15 13:21:01.365672334 +0200 CEST m=+42.007560511
task3: 2019-04-15 13:21:02.365795475 +0200 CEST m=+43.007683682
task3: 2019-04-15 13:21:03.365963353 +0200 CEST m=+44.007851518
task3: 2019-04-15 13:21:04.366108946 +0200 CEST m=+45.007997113
task3: 2019-04-15 13:21:05.366255758 +0200 CEST m=+46.008143950
task3: 2019-04-15 13:21:06.366433668 +0200 CEST m=+47.008321854
task3: 2019-04-15 13:21:07.366602436 +0200 CEST m=+48.008490616
task3: 2019-04-15 13:21:08.366788483 +0200 CEST m=+49.008676649
task3: 2019-04-15 13:21:09.366968318 +0200 CEST m=+50.008856527
task3: 2019-04-15 13:21:10.367136886 +0200 CEST m=+51.009025093
task2: 2019-04-15 13:21:10.367136886 +0200 CEST m=+51.009025093
task3: 2019-04-15 13:21:11.367303105 +0200 CEST m=+52.009191289

7. Přesnější řízení okamžiků, v nichž má být úloha spuštěna

V případě, že je nutné nějakou úlohu spouštět například každých deset sekund, lze si vypomoci malým trikem. Samotná funkce s implementovanou úlohou sice bude spouštěna každou sekundu, ovšem současně se bude funkci předávat i hodnota neustále se zvyšujícího čítače. Ve chvíli, kdy bude tato hodnota dělitelná deseti, spustí se skutečný kód úlohy (v našem případě implementovaný pouze jako volání funkce println):

func task3(t time.Time, counter int) {
        if counter%10 == 0 {
                println("task3:", t.String())
        }
}

Implementace čítače vyžaduje použití anonymní funkce, protože budeme muset zajistit, že se funkci task předá ještě jeden parametr, kromě samotného časového razítka:

task3cnt := 0
 
cron.NewDailyJob(cron.ANY, cron.ANY, cron.ANY,
                func(t time.Time) { task3cnt++; task3(t, task3cnt) })

Tato úprava je implementována v následujícím demonstračním příkladu, jehož zdrojový kód je dostupný na stránce https://github.com/tisnik/go-root/blob/master/article21/go-cron/jobs4.go:

package main
 
import (
        "github.com/rk/go-cron"
        "time"
)
 
func task1(t time.Time) {
        println("task1:", t.String())
}
 
func task2(t time.Time) {
        println("task2:", t.String())
}
 
func task3(t time.Time, counter int) {
        if counter%10 == 0 {
                println("task3:", t.String())
        }
}
 
func main() {
        task3cnt := 0
 
        c := make(chan bool)
        cron.NewDailyJob(cron.ANY, cron.ANY, 0, task1)
        cron.NewDailyJob(cron.ANY, cron.ANY, 10, task2)
        cron.NewDailyJob(cron.ANY, cron.ANY, cron.ANY,
                func(t time.Time) { task3cnt++; task3(t, task3cnt) })
 
        <-c
}

Podívejme se nyní na chování tohoto příkladu po spuštění. Můžeme vidět, že se třetí úloha (resp. přesněji řečeno část zapsaná v podmínce) skutečně spouští v desetisekundových intervalech

task1: 2019-04-11 20:48:00.342877606 +0200 CEST m=+4.001062857
task3: 2019-04-11 20:48:05.343610003 +0200 CEST m=+9.001795162
task2: 2019-04-11 20:48:10.344500095 +0200 CEST m=+14.002685300
task3: 2019-04-11 20:48:15.345315733 +0200 CEST m=+19.003500961
task3: 2019-04-11 20:48:25.346865887 +0200 CEST m=+29.005051058

8. Použití dalších funkcí knihovny go-cron pro naplánování úloh

V posledním příkladu použití knihovny go-cron je ukázán způsob volání dalších tří funkcí určených pro naplánování úlohy. Funkci NewDailyJob již známe, ovšem prakticky stejným způsobem můžeme použít i funkce NewWeeklyJob, NewMonthlyJob a nejobecnější funkci NewCronJob:

package main
 
import (
        "github.com/rk/go-cron"
        "time"
)
 
func task1(t time.Time) {
        println("task1:", t.String())
}
 
func task2(t time.Time) {
        println("task2:", t.String())
}
 
func task3(t time.Time) {
        println("task3:", t.String())
}
 
func main() {
        c := make(chan bool)
 
        cron.NewWeeklyJob(cron.ANY, 21, 05, 00, task1)
        cron.NewMonthlyJob(cron.ANY, 21, 05, 00, task2)
        cron.NewCronJob(cron.ANY, cron.ANY, cron.ANY, 21, 05, 00, task3)
 
        <-c
}
Poznámka: v tomto případě již nebudu ukazovat výsledek činnosti příkladu, protože by článek nestihl vyjít tak, jak byl naplánován :-)

9. Balíček clockwork

Druhou knihovnou určenou pro plánování úloh, s níž se v dnešním článku seznámíme, je knihovna se jménem Clockwork. Tato knihovna je založena na poněkud jiném principu, než go-cron, protože zde plánování úloh probíhá následujícím způsobem:

scheduler := clockwork.NewScheduler()
scheduler.Schedule().Every(4).Seconds().Do(task)
scheduler.Run()

Jména a pořadí funkcí je zvoleno tak, aby výsledný zápis připomínal anglickou větu. Navíc není nutné použít trik s kanálem pro to, aby se zabránilo ukončení gorutiny, v níž běží funkce main.

Instalaci knihovny Clockwork zajistíme standardním příkazem go get, konkrétně takto:

$ go get github.com/whiteShtef/clockwork

V předchozím úryvku kódu bylo ukázáno, že se nejprve vytvoří objekt typu scheduler a následně je již možné volat jeho metody, které se typicky řetězí, protože každá z metod vrací hodnotu typu *Job:

Metoda význam
Every specifikace frekvence opakování, musí být zadáno kladné číslo či žádný argument
At specifikace spuštění zadaná řetězcem (ukážeme si v demonstračním příkladu)
Do určení funkce s implementovanou úlohou
Second uvedeno za Every slouží ke specifikaci jednotky
Seconds dtto
Minute uvedeno za Every slouží ke specifikaci jednotky
Minutes dtto
Hour dtto
Hours dtto
Day dtto
Days dtto
Week dtto
Weeks dtto
Monday specifikace dne v týdnu
Tuesday specifikace dne v týdnu
Wednesday specifikace dne v týdnu
Thursday specifikace dne v týdnu
Friday specifikace dne v týdnu
Saturday specifikace dne v týdnu
Sunday specifikace dne v týdnu
Poznámka: jediným problémem, na který v praxi narazíte, je problematické plánování úloh v aktuálním týdnu, viz též https://github.com/whiteShtef/cloc­kwork/issues/10.

10. Spuštění periodicky se opakující úlohy

V prvním příkladu používajícím knihovnu Clockwork naplánujeme úlohu, která bude spuštěna každé čtyři sekundy. Samotné naplánování úlohy je provedeno na těchto třech řádcích:

scheduler := clockwork.NewScheduler()
scheduler.Schedule().Every(4).Seconds().Do(task)
scheduler.Run()

Samotná funkce představující úlohu je nyní zavolána a přitom jí nejsou předány žádné parametry, na rozdíl od příkladů předchozích:

func task() {
        println("task/job called")
}

Úplný zdrojový kód tohoto příkladu vypadá následovně:

package main
 
import (
        "github.com/whiteShtef/clockwork"
)
 
func task() {
        println("task/job called")
}
 
func main() {
        scheduler := clockwork.NewScheduler()
        scheduler.Schedule().Every(4).Seconds().Do(task)
        scheduler.Run()
}

Výsledek běhu tohoto příkladu:

Scheduled for  2019-04-15 20:33:26.890561981 +0200 CEST m=+4.002487729
Scheduled for  2019-04-15 20:33:30.890561981 +0200 CEST m=+8.002487729
task/job called
Scheduled for  2019-04-15 20:33:34.890561981 +0200 CEST m=+12.002487729
task/job called

Příklad ovšem můžeme upravit takovým způsobem, že se funkci Every nepředá žádná hodnota. To má stejný význam, jako bychom použili hodnotu 1. Navíc ještě namísto funkce Seconds zavoláme identicky se chovající funkci Second:

package main

import (
        "github.com/whiteShtef/clockwork"
)

func task() {
        println("task/job called")
}

func main() {
        scheduler := clockwork.NewScheduler()
        scheduler.Schedule().Every().Second().Do(task)
        scheduler.Run()
}

Výsledek:

Scheduled for  2019-04-15 20:37:30.548405696 +0200 CEST m=+1.001758537
Scheduled for  2019-04-15 20:37:31.548405696 +0200 CEST m=+2.001758537
task/job called
Scheduled for  2019-04-15 20:37:32.548405696 +0200 CEST m=+3.001758537
task/job called
Scheduled for  2019-04-15 20:37:33.548405696 +0200 CEST m=+4.001758537
task/job called

11. Naplánování většího množství úloh

Počet naplánovaných úloh není striktně omezen, takže v dalším příkladu naplánujeme periodické spouštění tří úloh, přičemž první úloha bude spouštěna každých třicet sekund, druhá úloha každých dvacet sekund a úloha třetí každou celou minutu:

package main
 
import (
        "github.com/whiteShtef/clockwork"
)
 
func task1() {
        println("task/job #1 called")
}
 
func task2() {
        println("task/job #2 called")
}
 
func task3() {
        println("task/job #3 called")
}
 
func main() {
        scheduler := clockwork.NewScheduler()
        scheduler.Schedule().Every(20).Seconds().Do(task1)
        scheduler.Schedule().Every(30).Seconds().Do(task2)
        scheduler.Schedule().Every().Minutes().Do(task3)
        scheduler.Run()
}

Příklad výstupu (v němž se nám ovšem pletou informace o stavu naplánování další úlohy):

Scheduled for  2019-04-12 19:42:04.678628129 +0200 CEST m=+20.002249265
Scheduled for  2019-04-12 19:42:14.678965746 +0200 CEST m=+30.002586819
Scheduled for  2019-04-12 19:42:44.678998274 +0200 CEST m=+60.002619364
Scheduled for  2019-04-12 19:42:24.678628129 +0200 CEST m=+40.002249265
task/job #1 called
Scheduled for  2019-04-12 19:42:44.678965746 +0200 CEST m=+60.002586819
task/job #2 called
Scheduled for  2019-04-12 19:42:44.678628129 +0200 CEST m=+60.002249265
task/job #1 called
Scheduled for  2019-04-12 19:43:04.678628129 +0200 CEST m=+80.002249265
Scheduled for  2019-04-12 19:43:14.678965746 +0200 CEST m=+90.002586819
Scheduled for  2019-04-12 19:43:44.678998274 +0200 CEST m=+120.002619364
task/job #1 called
task/job #3 called
task/job #2 called

V případě potřeby můžeme i v knihovně Clockwork použít trik, s nímž jsme se již seznámili v předchozích kapitolách – jedná se o počitadlo řídicí přesněji, kdy se má jaká úloha spustit. Většinou není tento trik nutný (na rozdíl od go-cron), takže jen pro úplnost:

package main
 
import (
        "github.com/whiteShtef/clockwork"
)
 
func task1() {
        println("task/job #1 called")
}
 
func task2() {
        println("task/job #2 called")
}
 
func task3(counter int) {
        if counter%10 == 0 {
                println("task/job #3 called")
        }
}
 
func main() {
        task3cnt := 0
 
        scheduler := clockwork.NewScheduler()
        scheduler.Schedule().Every(20).Seconds().Do(task1)
        scheduler.Schedule().Every(30).Seconds().Do(task2)
        scheduler.Schedule().Every().Seconds().Do(
                func() { task3cnt++; task3(task3cnt) })
 
        scheduler.Run()
}

12. Použití dalších metod pro naplánování úloh

V demonstračním příkladu, který naleznete na adrese https://github.com/tisnik/go-root/blob/master/article21/cloc­kwork/jobs5.go, je ukázán způsob použití dalších metod určených pro naplánování úloh. Použitý rozsah časových intervalů začíná na řádu sekund a končí týdny:

package main
 
import (
        "github.com/whiteShtef/clockwork"
)
 
func task1() {
        println("task/job #1 called")
}
 
func task2() {
        println("task/job #2 called")
}
 
func task3() {
        println("task/job #2 called")
}
 
func task4() {
        println("task/job #4 called")
}
 
func task5() {
        println("task/job #5 called")
}
 
func task6() {
        println("task/job #6 called")
}
 
func task7() {
        println("task/job #7 called")
}
 
func task8() {
        println("task/job #8 called")
}
 
func main() {
        scheduler := clockwork.NewScheduler()
        scheduler.Schedule().Every(30).Seconds().Do(task1)
        scheduler.Schedule().Every(30).Minutes().Do(task2)
        scheduler.Schedule().Every().Hours().Do(task3)
        scheduler.Schedule().Every(2).Days().Do(task4)
        scheduler.Schedule().Every(2).Days().At("23:59").Do(task5)
        scheduler.Schedule().Every(4).Weeks().Do(task6)
        scheduler.Schedule().Every().Tuesday().Do(task7)
        scheduler.Schedule().Every().Friday().At("19:55").Do(task8)
        scheduler.Run()
}

Výstup nyní může vypadat následovně:

Scheduled for  2019-04-12 19:53:42.402008929 +0200 CEST m=+30.001954737
Scheduled for  2019-04-12 20:23:12.402387894 +0200 CEST m=+1800.002333658
Scheduled for  2019-04-12 20:53:12.402419311 +0200 CEST m=+3600.002365068
Scheduled for  2019-04-14 00:00:00 +0200 CEST
Scheduled for  2019-04-14 23:59:00 +0200 CEST
Scheduled for  2019-05-10 19:53:12.402527275 +0200 CEST m=+2419200.002473084
Scheduled for  2019-04-16 00:00:00 +0200 CEST
Scheduled for  2019-04-19 19:55:00 +0200 CEST
Poznámka: opět v tomto případě nebudeme čekat na skutečné zavolání jednotlivých úloh.

13. Balíček clockwerk

Třetí knihovna, o níž se v dnešním článku zmíníme, se jmenuje Clockwerk a jak její název, tak i chování, se podobá výše zmíněné knihovně Clockwork.

I knihovna Clockwerk se instaluje standardním příkazem go get:

$ go get github.com/onatm/clockwerk

Časový interval se specifikuje buď v násobcích nějaké časové jednotky:

scheduler.Every(1 * time.Second).Do(task)

nebo pomocí hodnoty typu Duration popsané ve druhé a třetí kapitole:

duration, _ := time.ParseDuration("2s")
 
scheduler := clockwerk.New()
scheduler.Every(duration).Do(task)

V této knihovně je opět nutné zajistit, aby se hlavní gorutina předčasně neukončila, takže použijeme náš oblíbený trik s kanálem:

func main() {
        c := make(chan bool)
 
        var task Task
        scheduler := clockwerk.New()
        scheduler.Every(1 * time.Second).Do(task)
        scheduler.Start()
 
        <-c
}

14. Naplánování jedné úlohy

Vzhledem k podobnosti knihoven Clockwork a Clockwerk (a podobnost se netýká jen názvu těchto knihoven) bude popis druhé zmíněné knihovny již poměrně stručný. V následujícím příkladu je naplánováno spuštění úlohy s periodou jedné sekundy. Povšimněte si použití nového datového typu reprezentujícího úlohu:

package main
 
import (
        "github.com/onatm/clockwerk"
        "time"
)
 
type Task struct{}
 
func (t Task) Run() {
        println("task/job called")
}
 
func main() {
        c := make(chan bool)
 
        var task Task
        scheduler := clockwerk.New()
        scheduler.Every(1 * time.Second).Do(task)
        scheduler.Start()
 
        <-c
}

Příklad je velmi jednoduchý, takže se po jeho spuštění pouze začnou vypisovat informace o zavolání metody Run:

task/job called
task/job called
task/job called
...
...
...
Poznámka: v dalších příkladech již budeme zobrazovat i časová razítka.

15. Naplánování několika nezávislých úloh

Ve druhém příkladu spustíme dvě úlohy současně, každou pochopitelně s odlišnou periodou:

package main
 
import (
        "github.com/onatm/clockwerk"
        "time"
)
 
type Task1 struct{}
type Task2 struct{}
 
func (t Task1) Run() {
        println("task/job #1 called", time.Now().String())
}
 
func (t Task2) Run() {
        println("task/job #2 called", time.Now().String())
}
 
func main() {
        c := make(chan bool)
 
        var task1 Task1
        var task2 Task2
 
        scheduler := clockwerk.New()
        scheduler.Every(2 * time.Second).Do(task1)
        scheduler.Every(3 * time.Second).Do(task2)
        scheduler.Start()
 
        <-c
}

Výsledek je nyní podrobnější a můžeme z něj vidět, že první úloha je skutečně spouštěna častěji, než úloha druhá:

task/job #1 called 2019-04-15 13:04:34.219397549 +0200 CEST m=+2.000428334
task/job #2 called 2019-04-15 13:04:35.219443888 +0200 CEST m=+3.000474675
task/job #1 called 2019-04-15 13:04:36.219446535 +0200 CEST m=+4.000477320
task/job #2 called 2019-04-15 13:04:38.319475248 +0200 CEST m=+6.100506032
task/job #1 called 2019-04-15 13:04:38.319593899 +0200 CEST m=+6.100624681
task/job #1 called 2019-04-15 13:04:40.419388545 +0200 CEST m=+8.200419330
task/job #2 called 2019-04-15 13:04:41.419430345 +0200 CEST m=+9.200461144
task/job #1 called 2019-04-15 13:04:42.419400301 +0200 CEST m=+10.200431100
task/job #2 called 2019-04-15 13:04:44.519401501 +0200 CEST m=+12.300432286
task/job #1 called 2019-04-15 13:04:44.519473001 +0200 CEST m=+12.300503798
task/job #1 called 2019-04-15 13:04:46.619424601 +0200 CEST m=+14.400455397
...
...
...

16. Použití datového typu Duration při plánování úloh

Většinou je mnohem jednodušší než výpočty typu:

scheduler.Every(2 * time.Second).Do(task1)
scheduler.Every(3 * time.Second).Do(task2)

použít přímo typ Duration, jenž umožňuje zapsat časový interval v řetězci:

duration1, _ := time.ParseDuration("2s")
duration2, _ := time.ParseDuration("3s")
 
scheduler.Every(duration1).Do(task1)
scheduler.Every(duration2).Do(task2)

Tento postup je použitý v předposledním demonstračním příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article21/cloc­kwerk/jobs3.go:

package main
 
import (
        "github.com/onatm/clockwerk"
        "time"
)
 
type Task struct{}
 
func (t Task) Run() {
        println("task/job #1 called", time.Now().String())
}
 
func main() {
        c := make(chan bool)
 
        var task Task
 
        d, _ := time.ParseDuration("2s")
 
        scheduler := clockwerk.New()
        scheduler.Every(d).Do(task)
        scheduler.Start()
 
        <-c
}

Výsledek běhu tohoto příkladu ukazuje, že se skutečně použila perioda dvou sekund:

task/job #1 called 2019-04-15 13:05:32.247415431 +0200 CEST m=+2.000454760
task/job #1 called 2019-04-15 13:05:34.347451009 +0200 CEST m=+4.100490339
task/job #1 called 2019-04-15 13:05:36.447406587 +0200 CEST m=+6.200445933
...
...
...

17. Úprava příkladu s více úlohami

Dnešní poslední demonstrační příklad taktéž využívá datový typ Duration, tentokrát pro dvojici úloh s rozdílnou periodou spouštění:

package main
 
import (
        "github.com/onatm/clockwerk"
        "time"
)
 
type Task1 struct{}
type Task2 struct{}
 
func (t Task1) Run() {
        println("task/job #1 called", time.Now().String())
}
 
func (t Task2) Run() {
        println("task/job #2 called", time.Now().String())
}
 
func main() {
        c := make(chan bool)
 
        var task1 Task1
        var task2 Task2
 
        duration1, _ := time.ParseDuration("2s")
        duration2, _ := time.ParseDuration("3s")
 
        scheduler := clockwerk.New()
        scheduler.Every(duration1).Do(task1)
        scheduler.Every(duration2).Do(task2)
        scheduler.Start()
 
        <-c
}

Příklad výstupu:

task/job #1 called 2019-04-15 13:05:57.144960688 +0200 CEST m=+2.000520694
task/job #2 called 2019-04-15 13:05:58.144870964 +0200 CEST m=+3.000430958
task/job #1 called 2019-04-15 13:05:59.244838911 +0200 CEST m=+4.100398907
task/job #2 called 2019-04-15 13:06:01.244834227 +0200 CEST m=+6.100394247
task/job #1 called 2019-04-15 13:06:01.344863602 +0200 CEST m=+6.200423601
task/job #1 called 2019-04-15 13:06:03.344941817 +0200 CEST m=+8.200501879
...
...
...

18. Vylepšení plánování asynchronních úloh s balíčkem JobRunner

Pro složitější aplikace, v nichž je nutné úlohy jak plánovat, tak i sledovat, je určena knihovna nazvaná JobRunner, kterou naleznete na adrese https://github.com/bamzi/jobrunner. Popisem této knihovny, která uživatelům (či administrátorům) dokonce dává k dispozici konzoli s uživatelským rozhraním, se budeme zabývat v navazující části seriálu o programovacím jazyce Go.

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á přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Soubor Popis Cesta
1 duration1.go převod řetězce „1h“ odpovídajícího přesně jedné hodině na typ Duration https://github.com/tisnik/go-root/blob/master/article21/du­ration/duration1.go
2 duration2.go odlišný časový interval odpovídající jedné hodině, šesti minutám a deseti sekundám https://github.com/tisnik/go-root/blob/master/article21/du­ration/duration2.go
3 duration3.go použití kratšího časového intervalu, konkrétně 200ms https://github.com/tisnik/go-root/blob/master/article21/du­ration/duration3.go
4 duration4.go intervaly nanosekundové a mikrosekundové https://github.com/tisnik/go-root/blob/master/article21/du­ration/duration4.go
5 duration5.go zaokrouhlení časového intervalu o délce trvání 3 hodiny 15 minut na celé hodiny https://github.com/tisnik/go-root/blob/master/article21/du­ration/duration5.go
6 duration6.go zaokrouhlení časového intervalu na celé čtvrthodiny https://github.com/tisnik/go-root/blob/master/article21/du­ration/duration6.go
       
7 jobs1.go knihovna go-cron: úloha, která se volá periodicky každou sekundu https://github.com/tisnik/go-root/blob/master/article21/go-cron/jobs1.go
8 jobs2.go knihovna go-cron: úprava předchozího příkladu volajícího periodicky se opakující úlohu každou sekundu https://github.com/tisnik/go-root/blob/master/article21/go-cron/jobs2.go
9 jobs3.go knihovna go-cron: spuštění tří samostatně volaných periodických úloh https://github.com/tisnik/go-root/blob/master/article21/go-cron/jobs3.go
10 jobs4.go knihovna go-cron: přesnější řízení, kdy se má úloha spustit https://github.com/tisnik/go-root/blob/master/article21/go-cron/jobs4.go
11 jobs5.go knihovna go-cron: použití dalších funkcí knihovny go-cron pro naplánování úloh https://github.com/tisnik/go-root/blob/master/article21/go-cron/jobs5.go
       
12 jobs1.go knihovna clockwork: spuštění úlohy každé čtyři sekundy https://github.com/tisnik/go-root/blob/master/article21/cloc­kwork/jobs1.go
13 jobs2.go knihovna clockwork: spuštění úlohy s periodou jedné sekundy https://github.com/tisnik/go-root/blob/master/article21/cloc­kwork/jobs2.go
14 jobs3.go knihovna clockwork: naplánování většího množství úloh https://github.com/tisnik/go-root/blob/master/article21/cloc­kwork/jobs3.go
15 jobs4.go knihovna clockwork: přesnější řízení, kdy se má úloha spustit https://github.com/tisnik/go-root/blob/master/article21/cloc­kwork/jobs4.go
16 jobs5.go knihovna clockwork: další funkce určené pro naplánování úloh https://github.com/tisnik/go-root/blob/master/article21/cloc­kwork/jobs5.go
       
17 jobs1.go knihovna clockwerk: spuštění úlohy s periodou jedné sekundy https://github.com/tisnik/go-root/blob/master/article21/cloc­kwerk/jobs1.go
18 jobs2.go knihovna clockwerk: spuštění dvou úloh s odlišnou periodou https://github.com/tisnik/go-root/blob/master/article21/cloc­kwerk/jobs2.go
19 jobs3.go knihovna clockwerk: spuštění jedné úlohy, použití Duration https://github.com/tisnik/go-root/blob/master/article21/cloc­kwerk/jobs3.go
20 jobs4.go knihovna clockwerk: spuštění dvou úloh s odlišnou periodou, použití Duration https://github.com/tisnik/go-root/blob/master/article21/cloc­kwerk/jobs4.go

20. Odkazy na Internetu

  1. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  2. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  3. go-cron
    https://github.com/rk/go-cron
  4. gocron
    https://github.com/jasonlvhit/gocron
  5. clockwork
    https://github.com/whiteShtef/cloc­kwork
  6. clockwerk
    https://github.com/onatm/clockwerk
  7. JobRunner
    https://github.com/bamzi/jobrunner
  8. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  9. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  10. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  11. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  12. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  13. go-prompt
    https://github.com/c-bata/go-prompt
  14. readline
    https://github.com/chzyer/readline
  15. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  16. go-readline
    https://github.com/fiorix/go-readline
  17. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  18. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  19. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  20. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  21. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  22. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  23. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  24. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  25. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  26. Editline Library (libedit)
    http://thrysoee.dk/editline/
  27. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  28. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  29. WinEditLine
    http://mingweditline.sourceforge.net/
  30. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  31. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  32. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  33. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  34. history(3) – Linux man page
    https://linux.die.net/man/3/history
  35. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  36. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  37. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  38. Balíček ogletest
    https://github.com/jacobsa/ogletest
  39. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  40. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  41. package testing
    https://golang.org/pkg/testing/
  42. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  43. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  44. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  45. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  46. GoConvey
    http://goconvey.co/
  47. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  48. 5 simple tips and tricks for writing unit tests in #golang
    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742
  49. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  50. package gg
    https://godoc.org/github.com/fo­gleman/gg
  51. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  52. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  53. The Go image package
    https://blog.golang.org/go-image-package
  54. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  55. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  56. YAML
    https://yaml.org/
  57. edn
    https://github.com/edn-format/edn
  58. Smile
    https://github.com/FasterXML/smile-format-specification
  59. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  60. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  61. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  62. Introducing JSON
    http://json.org/
  63. Package json
    https://golang.org/pkg/encoding/json/
  64. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  65. Go by Example: JSON
    https://gobyexample.com/json
  66. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  67. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  68. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  69. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  70. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  71. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  72. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  73. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  74. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  75. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  76. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  77. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  78. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  79. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  80. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  81. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  82. Algorithms to Go
    https://yourbasic.org/
  83. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  84. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  85. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  86. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  87. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  88. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  89. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  90. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  91. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  92. The Go Programming Language (home page)
    https://golang.org/
  93. GoDoc
    https://godoc.org/
  94. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  95. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  96. The Go Programming Language Specification
    https://golang.org/ref/spec
  97. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  98. Package builtin
    https://golang.org/pkg/builtin/
  99. Package fmt
    https://golang.org/pkg/fmt/
  100. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  101. 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
  102. Learning Go
    https://www.miek.nl/go/
  103. Go Bootcamp
    http://www.golangbootcamp.com/
  104. 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
  105. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  106. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  107. The Go Blog
    https://blog.golang.org/
  108. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  109. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  110. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  111. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  112. 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
  113. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  114. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  115. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  116. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  117. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  118. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  119. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  120. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  121. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  122. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  123. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  124. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  125. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  126. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  127. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  128. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  129. 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/
  130. 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
  131. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  132. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  133. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  134. Go vs. Python
    https://www.peterbe.com/plog/govspy
  135. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  136. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  137. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  138. Go by Example: Slices
    https://gobyexample.com/slices
  139. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  140. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  141. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  142. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  143. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  144. nils In Go
    https://go101.org/article/nil.html
  145. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  146. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  147. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  148. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  149. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  150. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  151. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  152. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  153. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  154. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  155. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  156. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  157. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  158. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  159. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  160. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  161. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  162. 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
  163. 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
  164. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  165. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  166. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  167. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  168. Selectors
    https://golang.org/ref/spec#Selectors
  169. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  170. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  171. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  172. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  173. Part 21: Goroutines
    https://golangbot.com/goroutines/
  174. Part 22: Channels
    https://golangbot.com/channels/
  175. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  176. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  177. 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/
  178. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  179. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  180. Control Structures
    https://www.golang-book.com/books/intro/5
  181. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  182. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  183. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  184. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  185. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  186. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  187. Effective Go
    https://golang.org/doc/ef­fective_go.html
  188. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  189. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation