Grondbeginselen van Goroutines
Goroutine
Wanneer men Gophers vraagt naar de voordelen van Golang, komt men vaak artikelen tegen over concurrency. De basis hiervan is de lichtgewicht en eenvoudig te verwerken goroutine. Hierover heb ik een korte beschrijving opgesteld.
Concurrency vs. Parallelism
Voordat we goroutines begrijpen, wil ik eerst twee vaak verwarde concepten behandelen.
- Concurrency: Concurrency gaat over het verwerken van veel taken tegelijk. Het betekent niet noodzakelijkerwijs dat ze daadwerkelijk gelijktijdig worden uitgevoerd, maar eerder dat verschillende taken in kleinere eenheden worden verdeeld en afwisselend worden uitgevoerd, zodat het voor de gebruiker lijkt alsof meerdere taken tegelijkertijd worden verwerkt. Dit is een structureel en logisch concept. Concurrency is ook mogelijk op een single-core systeem.
- Parallelism: Parallelism is "het gelijktijdig verwerken van meerdere taken op meerdere cores". Het betekent letterlijk het parallel uitvoeren van taken, waarbij verschillende taken gelijktijdig worden uitgevoerd.
goroutines maken het eenvoudig om concurrency te implementeren via de Go runtime scheduler, en benutten op natuurlijke wijze parallelism via de GOMAXPROCS instelling.
De veelgebruikte multi-threading in Java is een typisch voorbeeld van parallelism.
Waarom zijn goroutines zo voordelig?
Lichtgewicht
De aanmaakkosten zijn zeer laag in vergelijking met andere talen. Men vraagt zich dan af waarom Golang zo weinig gebruikt? Dit komt doordat de aanmaaklocatie binnen de Go runtime wordt beheerd. Dit is te danken aan het feit dat het een lichtgewicht logische thread is, kleiner dan een OS-thread eenheid, en een initiële stack van ongeveer 2KB vereist. Deze stack is dynamisch en kan worden uitgebreid afhankelijk van de implementatie van de gebruiker.
Door het beheer in stack-eenheden is het aanmaken en verwijderen zeer snel en goedkoop, waardoor miljoenen goroutines kunnen worden uitgevoerd zonder dat dit een belasting vormt. Hierdoor kunnen goroutines dankzij de runtime scheduler de betrokkenheid van de OS-kernel minimaliseren.
Goede prestaties
Ten eerste vereisen goroutines, zoals hierboven beschreven, minimale betrokkenheid van de OS-kernel. Hierdoor zijn de kosten voor context switching op gebruikersniveau lager dan die van OS-threads, wat resulteert in een snellere taakwisseling.
Bovendien worden goroutines beheerd door ze toe te wijzen aan OS-threads met behulp van het M:N-model. Door een pool van OS-threads te creëren, kunnen taken met minder threads worden afgehandeld zonder dat veel threads nodig zijn. Als een goroutine bijvoorbeeld in een wachtstatus terechtkomt, zoals bij een systeemoproep, zal de Go runtime een andere goroutine op de OS-thread uitvoeren. Hierdoor blijft de OS-thread efficiënt gebruikmaken van de CPU zonder stil te vallen, wat resulteert in een snelle verwerking.
Hierdoor kan Golang, met name bij I/O-bewerkingen, hogere prestaties leveren dan andere talen.
Beknopt
Het gemak waarmee een functie kan worden afgehandeld met slechts één go-keyword wanneer concurrency vereist is, is een groot voordeel.
Complexe Locks zoals Mutex en Semaphore moeten worden gebruikt, en bij het gebruik van Locks is het onvermijdelijk om rekening te houden met deadlock-situaties, wat een complexe ontwerpfase voorafgaand aan de ontwikkeling vereist.
goroutines bevelen de overdracht van gegevens via Channel aan, in lijn met de filosofie "Deel geen geheugen door te communiceren, maar communiceer om geheugen te delen". SELECT ondersteunt zelfs de functionaliteit om, in combinatie met Channel, gegevens te verwerken vanuit het kanaal dat het eerst gereed is. Bovendien kan sync.WaitGroup worden gebruikt om eenvoudig te wachten tot alle goroutines zijn voltooid, waardoor de werkstroom gemakkelijk kan worden beheerd. Dankzij deze hulpmiddelen kunnen problemen met data-races tussen threads worden voorkomen en kan concurrency veiliger worden afgehandeld.
Bovendien kan de levenscyclus, annulering, time-out, deadline en reikwijdte van verzoeken op gebruikersniveau worden beheerd met behulp van context, wat een zekere mate van stabiliteit garandeert.
Parallelle taken van Goroutine (GOMAXPROCS)
Hoewel ik de voordelen van concurrency van goroutines heb benadrukt, zult u zich waarschijnlijk afvragen of parallelism niet wordt ondersteund. Het aantal cores in moderne CPU's overschrijdt tegenwoordig de dubbele cijfers, en zelfs thuis-pc's hebben een aanzienlijk aantal cores.
goroutines voeren echter ook parallelle taken uit, en dat is GOMAXPROCS.
Als GOMAXPROCS niet is ingesteld, varieert de standaardinstelling per versie.
Voor 1.5: Standaardwaarde 1. Indien meer dan 1 nodig is, is het verplicht om dit in te stellen met
runtime.GOMAXPOCS(runtime.NumCPU()).1.5 tot 1.24: Dit is gewijzigd naar alle beschikbare logische cores. Vanaf dit moment hoeft de ontwikkelaar dit niet langer in te stellen, tenzij er specifieke beperkingen zijn.
1.25: Als een taal die bekend is in containeromgevingen, controleert het de cGroup in Linux om de
CPU-beperkingte bepalen die is ingesteld voor de container.Als het aantal logische cores 10 is en de CPU-beperking 5, dan wordt
GOMAXPROCSingesteld op de lagere waarde van 5.
De wijziging in 1.25 is zeer significant. Dit komt doordat de bruikbaarheid van de taal in containeromgevingen is toegenomen. Hierdoor kunnen onnodige thread-creatie en context switching worden verminderd, waardoor CPU-throttling kan worden voorkomen.
1package main
2
3import (
4 "fmt"
5 "math/rand"
6 "runtime"
7 "time"
8)
9
10func exe(name int, wg *sync.WaitGroup) {
11 defer wg.Done()
12
13 fmt.Printf("Goroutine %d: 시작\n", name) // Goroutine %d: Start
14 time.Sleep(10 * time.Millisecond) // Vertraging voor taaksimulatie
15 fmt.Printf("Goroutine %d: 시작\n", name) // Goroutine %d: Start
16}
17
18func main() {
19 runtime.GOMAXPROCS(2) // Gebruik slechts 2 CPU-cores
20 wg := sync.WaitGroup();
21 goroutineCount := 10
22 wg.Add(goroutineCount)
23
24 for i := 0; i < goroutineCount; i++ {
25 go exe(i, &wg)
26 }
27
28 fmt.Println("모든 goroutine이 끝날 때까지 대기합니다...") // Wachten tot alle goroutines zijn voltooid...
29 wg.Wait()
30 fmt.Println("모든 작업이 완료되었습니다.") // Alle taken zijn voltooid.
31
32}
33
De Scheduler van Goroutine (M:N-model)
In het voorgaande deel, waar werd uitgelegd dat goroutines worden toegewezen aan en beheerd door OS-threads met behulp van het M:N-model, gaan we nu dieper in op het goroutine GMP-model.
- G (Goroutine): De kleinste werkeenheid die in Go wordt uitgevoerd.
- M (Machine): OS-thread (de daadwerkelijke werklocatie).
- P (Processor): De logische processor die door de Go runtime wordt beheerd.
P heeft bovendien een lokale uitvoeringswachtrij (Local Run Queue) en fungeert als scheduler die de toegewezen G aan M toewijst. Kort gezegd:
De werking van GMP is als volgt:
- Wanneer een G (Goroutine) wordt aangemaakt, wordt deze toegewezen aan de lokale uitvoeringswachtrij van P (Processor).
- P (Processor) wijst de G (Goroutine) in de lokale uitvoeringswachtrij toe aan M (Machine).
- M (Machine) retourneert de status van G (Goroutine): block, complete of preempted.
- Work-Stealing: Als de lokale uitvoeringswachtrij van P leeg raakt, controleert een andere P de globale wachtrij. Als er daar ook geen G (Goroutine) is, steelt deze taken van een andere lokale P (Processor) om ervoor te zorgen dat alle M's onafgebroken werken.
- Systeemoproepverwerking (Blocking): Als een G (Goroutine) tijdens de uitvoering geblokkeerd raakt, komt M (Machine) in een wachtstatus. Op dat moment scheidt P (Processor) zich van de geblokkeerde M (Machine) en combineert zich met een andere M (Machine) om de volgende G (Goroutine) uit te voeren. Hierdoor is er geen CPU-verspilling, zelfs niet tijdens wachttijden bij I/O-bewerkingen.
- Als een G (Goroutine) gedurende lange tijd wordt gepreëmteerd, krijgt een andere G (Goroutine) de kans om te worden uitgevoerd.
De Golang GC (Garbage Collector) wordt ook uitgevoerd op goroutines, waardoor geheugen parallel kan worden opgeruimd met minimale onderbreking van de applicatie (STW), wat resulteert in een efficiënt gebruik van systeembronnen.
Tot slot is Golang een van de grote sterke punten van de taal, en er zijn er nog veel meer. Ik hoop dat veel ontwikkelaars van Golang zullen genieten.
Dank u.