Goroutines zijn lichte uitvoeringsdraden die in de Go-architectuur zijn ingebouwd sinds de eerste versies om efficiënte concurrentie te bereiken. Historisch gezien ontstond het idee van lightweight-thread als een manier om de kosten van systeemdraden te omzeilen, evenals door de hoge vraag naar schaalbare serverapplicaties. Go is oorspronkelijk ontworpen als een taal voor server- en netwerksystemen, waarin miljoenen taken gelijktijdig moeten worden verwerkt.
Probleem: Concurrentie kan snel leiden tot race conditions, deadlocks en verhoogd geheugengebruik als de levenscyclus van goroutines niet wordt gecontroleerd, hun planning niet wordt overwogen en hun afsluiting niet wordt beheerd.
Oplossing: Goroutines worden gestart met het sleutelwoord go. Het werk van goroutines wordt gepland door de Go-scheduler, die gebruikmaakt van een M:N-model (M OS-draden bedienen N goroutines in de Go-taal). Voor het beheren van de levenscyclus worden kanalen, WaitGroup, context en controle van het sluiten van kanalen gebruikt.
Voorbeeldcode:
package main import ("fmt"; "time") func worker(id int) { fmt.Printf("Worker %d gestart ", id) time.Sleep(time.Second) fmt.Printf("Worker %d klaar ", id) } func main() { for i := 1; i <= 3; i++ { go worker(i) } time.Sleep(2 * time.Second) }
Belangrijke kenmerken:
Als in main de goroutine niet expliciet wordt afgewacht, zal deze altijd worden uitgevoerd?
Nee, de uitvoering van main eindigt — het proces wordt beëindigd ongeacht de staat van de kindergoroutines, en niet alle taken zullen worden uitgevoerd.
Is het starten van go func(...) uit een loop een garantie dat elke goroutine zijn eigen waarde van de loopvariabelen krijgt?
Nee, er ontstaat een probleem met het vastleggen van de loopvariabele, goroutines kunnen met dezelfde waarde van de slice/variabele werken. Het is nodig om de variabele te kopiëren, bijvoorbeeld door deze als argument door te geven:
for i := 0; i < 3; i++ { go func(n int) { fmt.Println(n) }(i) }
Kan één goroutine de Go-scheduler blokkeren en andere goroutines verhinderen om te draaien?
Ja, als deze een oneindige of zeer zware loop zonder schakelpunt (bijvoorbeeld zonder tijdsfunctie-aanroepen of yield) start, kan ze de OS-draad vasthouden — hoewel dit indruist tegen de ideologie van Go over "coöperatieve multitasking". Bijvoorbeeld, een zware functie zonder blokkeringen:
func busy() { for { // Geen wachten of blokkeringen } }
In een microservice wordt periodeel een goroutine gestart die uit de database leest, maar vergeten wordt deze af te sluiten bij annulering van het verzoek. Als gevolg hiervan blijven "hangende" goroutines over, die na verloop van tijd leiden tot het verbruiken van al het RAM.
Voordelen:
Nadelen:
Er wordt context gebruikt voor het controleren van taakannulering, WaitGroup — voor het beheren van de afsluiting van alle goroutines voordat de applicatie wordt gestopt, en kanalen — voor correcte gegevensoverdracht tussen uitvoerders.
Voordelen:
Nadelen: