Basisprincipes van Goroutines
Goroutine
Wanneer men Gophers vraagt om de voordelen van golang te bespreken, is er vaak een artikel over Concurrency dat ter sprake komt. De basis van die inhoud is de lichtgewicht en eenvoudig te verwerken goroutine. Ik heb hierover een korte tekst opgesteld.
Concurrency vs Parallelism
Voordat we goroutines begrijpen, wil ik eerst twee concepten verduidelijken die vaak door elkaar worden gehaald.
- Concurrency: Concurrency gaat over het gelijktijdig verwerken van veel taken. Dit betekent niet noodzakelijkerwijs dat ze daadwerkelijk tegelijkertijd worden uitgevoerd, maar het is een structureel en logisch concept waarbij meerdere taken in kleine eenheden worden verdeeld en afwisselend worden uitgevoerd, waardoor het voor de gebruiker lijkt alsof meerdere taken tegelijkertijd worden verwerkt. Concurrency is ook mogelijk op een single-core systeem.
- Parallelism: Parallelism is "het gelijktijdig verwerken van meerdere taken op meerdere cores". Dit betekent letterlijk dat taken parallel worden uitgevoerd, waarbij verschillende taken tegelijkertijd worden uitgevoerd.
Goroutines stellen ons in staat om eenvoudig Concurrency te implementeren via de Go runtime scheduler en maken op natuurlijke wijze gebruik van Parallelism door middel van de GOMAXPROCS
instelling.
De Multi thread van Java, dat vaak een hoge bezettingsgraad heeft, is een representatief concept van Parallelism.
Waarom is een Goroutine goed?
Lightweight
De creatiekosten zijn zeer laag in vergelijking met andere talen. Hier rijst de vraag waarom golang er minder gebruikt? Dit komt doordat de aanmaaklocatie intern door de Go runtime wordt beheerd. Dit is te wijten aan het feit dat het een lichte logische thread is; het is kleiner dan de eenheid van een OS-thread, vereist een initiële stackgrootte van ongeveer 2KB en kan dynamisch variëren door de stack aan te passen op basis van de implementatie van de gebruiker.
Doordat het op stack-eenheden wordt beheerd, is het aanmaken en verwijderen zeer snel en goedkoop, waardoor de verwerking van miljoenen goroutines geen last vormt. Hierdoor kan Goroutine, dankzij de runtime scheduler, de betrokkenheid van de OS kernel minimaliseren.
Performance
Ten eerste, zoals hierboven beschreven, heeft Goroutine minder betrokkenheid van de OS kernel, waardoor de kosten voor Context Switching op User-Level lager zijn dan bij een OS-thread eenheid, wat een snellere taakwisseling mogelijk maakt.
Bovendien wordt het beheerd door het toe te wijzen aan OS-threads met behulp van het M:N-model. Door een OS-thread pool te creëren, kan de verwerking worden uitgevoerd met minder threads zonder dat er veel threads nodig zijn. Als een Goroutine bijvoorbeeld in een wachttoestand raakt, zoals bij een Systeemoproep, voert de Go runtime een andere goroutine uit op de OS-thread, waardoor de OS-thread niet stilstaat en de CPU efficiënt wordt benut voor snelle verwerking.
Hierdoor kan Golang met name bij I/O-taken hogere Performance leveren in vergelijking met andere talen.
Concise
Een groot voordeel is ook dat een functie eenvoudig kan worden verwerkt met slechts het go
keyword wanneer Concurrency vereist is.
Men moet gebruikmaken van complexe Locks zoals Mutex
en Semaphore
, en het gebruik van Locks vereist onvermijdelijk de overweging van de DeadLock-status, wat leidt tot complexe stappen vanaf de ontwerpfase vóór de ontwikkeling.
Goroutine volgt de filosofie "Niet communiceren door geheugen te delen, maar geheugen delen door te communiceren" en beveelt datatransmissie via Channel
s aan. SELECT
, in combinatie met een Channel, ondersteunt zelfs de functionaliteit om te verwerken op basis van het Channel waarvan de data gereed is. Bovendien kan men met sync.WaitGroup
eenvoudig wachten tot alle goroutines zijn voltooid, waardoor de werkstroom gemakkelijk kan worden beheerd. Dankzij deze tools wordt het probleem van dataconcurrentie tussen threads voorkomen en is een veiligere Concurrency-verwerking mogelijk.
Daarnaast kan men met de Context op User-Level de levenscyclus, annulering, Time-out, Deadline en het bereik van de aanvraag beheersen, wat een zekere mate van stabiliteit waarborgt.
Parallelle verwerking van Goroutine (GOMAXPROCS)
Hoewel ik de voordelen van Concurrency van goroutine heb besproken, zult u zich afvragen of Parallelism wordt ondersteund. Het aantal cores in moderne CPU's overschrijdt tegenwoordig de dubbele cijfers, en zelfs huishoudelijke pc's hebben een aanzienlijk aantal cores.
Maar Goroutine voert ook Parallelle taken uit, en dat is GOMAXPROCS
.
Als GOMAXPROCS
niet is ingesteld, wordt deze per versie anders geconfigureerd.
Vóór 1.5: Standaardwaarde 1. Indien 1 of meer vereist is, was instelling op de manier zoals
runtime.GOMAXPOCS(runtime.NumCPU())
verplicht.1.5 tot 1.24: Dit werd gewijzigd naar het aantal beschikbare logische cores. Vanaf dit moment hoeft de ontwikkelaar dit niet meer in te stellen, tenzij er een sterke beperking nodig is.
1.25: Als een taal die bekend is in containeromgevingen, controleert het de cGroup op linux en verifieert het de
CPU-beperking
die is ingesteld voor de container.Als het aantal logische cores 10 is en de CPU-beperking 5, stelt
GOMAXPROCS
in op het lagere getal 5.
De wijziging in 1.25 is een zeer significante aanpassing. Dit komt doordat het taalgebruik in containeromgevingen is toegenomen. Dit heeft het mogelijk gemaakt om onnodige thread-creatie en Context Switching te verminderen, waardoor CPU Throttling wordt 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) // CPU 코어 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이 끝날 때까지 대기합니다...") // Wacht 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)
Als we iets specifieker ingaan op het eerdere punt over het M:N-model dat wordt gebruikt om het beheer toe te wijzen aan OS-threads, is er het goroutine GMP-model.
- G (Goroutine): De kleinste werkeenheid die in Go wordt uitgevoerd
- M (Machine): OS-thread (de feitelijke werklocatie)
- P (Processor): De logische Processor die door de Go runtime wordt beheerd
P heeft ook een Local Run Queue en fungeert als de Scheduler die de toegewezen G's aan M toewijst. Kortom, goroutine...
Het werkingsproces van GMP is als volgt:
- Wanneer een G (Goroutine) wordt aangemaakt, wordt deze toegewezen aan de Local Run Queue van P (Processor).
- P (Processor) wijst de G (Goroutine) in de Local Run Queue toe aan M (Machine).
- M (Machine) retourneert de status van G (Goroutine), namelijk block, complete of preempted.
- Work-Stealing: Als de Local Run Queue van P leeg raakt, controleert een andere P de Global Queue. Als daar ook geen G (Goroutine) is, steelt het werk van een andere lokale P (Processor) om ervoor te zorgen dat alle M's blijven werken.
- Systeemoproepverwerking (Blocking): Als G (Goroutine) tijdens de uitvoering geblokkeerd raakt, komt M (Machine) in een wachttoestand. Op dat moment scheidt P (Processor) zich van de geblokkeerde M (Machine) en combineert met een andere M (Machine) om de volgende G (Goroutine) uit te voeren. Hierdoor is er geen CPU-verspilling tijdens de wachttijd van I/O-taken.
- Als één G (Goroutine) langdurig wordt gepreempt (preempted), krijgt een andere G (Goroutine) de kans om te worden uitgevoerd.
De GC (Garbage Collector) van Golang wordt ook uitgevoerd bovenop Goroutine, waardoor het geheugen parallel kan worden opgeruimd met minimale onderbreking van de applicatie-uitvoering (STW), wat resulteert in een efficiënt gebruik van systeembronnen.
Tot slot is Golang een van de sterke punten van de taal, en er zijn er nog veel meer, dus ik hoop dat veel ontwikkelaars van Golang zullen genieten.
Dank u wel.