Obsah
1. Knihovny pro Go umožňující naplánování a spouštění periodických úloh
2. Datový typ Duration ze standardního balíčku fmt
3. Ukázky použití funkcí a metod datového typu Duration
5. Základní použití knihovny go-cron pro naplánování úloh
6. Naplánování většího množství úloh v jediném procesu
7. Přesnější řízení okamžiků, v nichž má být úloha spuštěna
8. Použití dalších funkcí knihovny go-cron pro naplánování úloh
10. Spuštění periodicky se opakující úlohy
11. Naplánování většího množství úloh
12. Použití dalších metod pro naplánování úloh
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
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
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
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
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 |
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)
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 }
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 |
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/clockwork/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
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 ... ... ...
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/clockwerk/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/duration/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/duration/duration2.go |
3 | duration3.go | použití kratšího časového intervalu, konkrétně 200ms | https://github.com/tisnik/go-root/blob/master/article21/duration/duration3.go |
4 | duration4.go | intervaly nanosekundové a mikrosekundové | https://github.com/tisnik/go-root/blob/master/article21/duration/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/duration/duration5.go |
6 | duration6.go | zaokrouhlení časového intervalu na celé čtvrthodiny | https://github.com/tisnik/go-root/blob/master/article21/duration/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/clockwork/jobs1.go |
13 | jobs2.go | knihovna clockwork: spuštění úlohy s periodou jedné sekundy | https://github.com/tisnik/go-root/blob/master/article21/clockwork/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/clockwork/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/clockwork/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/clockwork/jobs5.go |
17 | jobs1.go | knihovna clockwerk: spuštění úlohy s periodou jedné sekundy | https://github.com/tisnik/go-root/blob/master/article21/clockwerk/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/clockwerk/jobs2.go |
19 | jobs3.go | knihovna clockwerk: spuštění jedné úlohy, použití Duration | https://github.com/tisnik/go-root/blob/master/article21/clockwerk/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/clockwerk/jobs4.go |
20. Odkazy na Internetu
- VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 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 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - 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/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - 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 - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - 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/ - 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 - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - 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 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - 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/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation