Basisprincipes van Goroutines
Goroutine
Wanneer men Gophers vraagt naar de voordelen van Golang, komt vaak de concurrency ter sprake. De basis hiervan is de lichtgewicht en eenvoudig te verwerken goroutine. Hierover heb ik een korte beschrijving opgesteld.
Concurrency versus Parallelism
Voordat we goroutines begrijpen, wil ik eerst twee veelvoorkomende, verwarrende concepten toelichten.
- 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, zodat het voor de gebruiker lijkt alsof meerdere taken gelijktijdig worden verwerkt. Concurrency is ook mogelijk op een single-core systeem.
- Parallelism: Parallelism betekent "het gelijktijdig verwerken van meerdere taken op meerdere cores". Het betreft 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 door middel van de GOMAXPROCS
instelling.
De multithreading van Java, die vaak een hoge benutting kent, is een representatief concept van parallelism.
Waarom is een goroutine gunstig?
Lichtgewicht
De creatiekosten zijn zeer laag in vergelijking met andere talen. De vraag rijst waarom Golang hier zo weinig van gebruikt. Dit komt doordat de aanmaaklocatie intern door de Go runtime wordt beheerd. Dit is te danken aan de eerder genoemde lichtgewicht logische thread, die kleiner is dan een OS-threadeenheid en een initiële stackgrootte van ongeveer 2KB vereist, dynamisch variabel door het toevoegen van stackruimte afhankelijk van de implementatie van de gebruiker.
Doordat het beheer op stackniveau plaatsvindt, is het creëren en verwijderen zeer snel en kostenefficiënt, waardoor miljoenen goroutines zonder merkbare overhead kunnen worden verwerkt. Hierdoor kan Goroutine, dankzij de runtime scheduler, de tussenkomst van de OS-kernel tot een minimum beperken.
Prestatie
Ten eerste, zoals hierboven beschreven, vereist Goroutine minder tussenkomst van de OS-kernel. Hierdoor zijn de kosten voor context switching op gebruikersniveau (User-Level) lager dan die van een OS-threadeenheid, wat een snelle taakwisseling mogelijk maakt.
Daarnaast wordt het beheerd door toewijzing aan OS-threads via het M:N-model. Door een OS-threadpool te creëren, kunnen taken worden verwerkt met een kleiner aantal threads dan anders nodig zou zijn. Wanneer een Goroutine bijvoorbeeld in een wachtstatus terechtkomt, zoals bij een systeemoproep, zal de Go runtime een andere goroutine op de OS-thread uitvoeren, waardoor de OS-thread niet stilvalt en de CPU efficiënt wordt benut voor snelle verwerking.
Hierdoor kan Golang met name bij I/O-bewerkingen hogere prestaties leveren dan andere talen.
Beknopt
Een groot voordeel is dat functies eenvoudig kunnen worden verwerkt met slechts één go
-keyword wanneer concurrency vereist is.
Het gebruik van complexe Locks, zoals Mutex
en Semaphore
, is noodzakelijk, en bij het gebruik van Locks moet onvermijdelijk rekening worden gehouden met Deadlock-situaties, wat al in de ontwerpfase vóór de ontwikkeling complexe stappen vereist.
Goroutine moedigt gegevensoverdracht via Channel
aan, in lijn met de filosofie "Deel geen geheugen door te communiceren, maar communiceer om geheugen te delen." SELECT
biedt, in combinatie met Channel
, de functionaliteit om gegevens te verwerken van het kanaal dat als eerste gereed is. Bovendien maakt sync.WaitGroup
het eenvoudig om te wachten tot alle goroutines zijn voltooid, waardoor de werkstroom gemakkelijk kan worden beheerd. Dankzij deze hulpmiddelen worden problemen met datacontentie tussen threads voorkomen en is veiligere concurrency-verwerking mogelijk.
Bovendien kan door middel van context
de levenscyclus, annulering, time-out, deadline en reikwijdte van verzoeken op gebruikersniveau (User-Level) worden beheerd, wat een zekere mate van stabiliteit garandeert.
Parallelle verwerking van Goroutine (GOMAXPROCS)
Hoewel ik de voordelen van concurrency van goroutines heb benoemd, vraagt u zich misschien af of parallelism niet wordt ondersteund. Het aantal CPU-kernen is recentelijk toegenomen tot meer dan twintig, en zelfs thuis-pc's hebben een aanzienlijk aantal cores.
Echter, Goroutine ondersteunt wel degelijk parallelle verwerking, namelijk via GOMAXPROCS
.
Indien GOMAXPROCS
niet is ingesteld, wordt de waarde per versie anders geconfigureerd.
Voor 1.5: Standaardwaarde 1. Indien meer dan 1 nodig is, is instelling verplicht, bijvoorbeeld via
runtime.GOMAXPOCS(runtime.NumCPU())
.1.5 tot en met 1.24: Gewijzigd naar het aantal beschikbare logische cores. Vanaf dit moment hoeft de ontwikkelaar geen instellingen meer te doen, tenzij er specifieke beperkingen zijn.
1.25: Als een taal die bekend is in containeromgevingen, controleert deze cGroup op Linux om de
CPU-beperking
in de container te bepalen.Als het aantal logische cores dan 10 is en de CPU-beperking 5, wordt
GOMAXPROCS
ingesteld op de lagere waarde van 5.
De wijziging in 1.25 is van aanzienlijk belang, aangezien de bruikbaarheid van de taal in containeromgevingen hierdoor is toegenomen. Dit heeft geleid tot een vermindering van onnodige thread-creatie en context-switching, waardoor CPU-throttling kan worden voorkomen.
1package main
2
3import (
4 "fmt"
5 "rand" // random is niet nodig, maar wel "sync"
6 "runtime"
7 "time"
8 "sync" // <-- Deze moet toegevoegd worden
9)
10
11func exe(name int, wg *sync.WaitGroup) {
12 defer wg.Done()
13
14 fmt.Printf("Goroutine %d: 시작\n", name)
15 time.Sleep(10 * time.Millisecond) // 작업 시뮬레이션을 위한 지연
16 fmt.Printf("Goroutine %d: 시작\n", name)
17}
18
19func main() {
20 runtime.GOMAXPROCS(2) // CPU 코어 2개만 사용
21 wg := sync.WaitGroup();
22 goroutineCount := 10
23 wg.Add(goroutineCount)
24
25 for i := 0; i < goroutineCount; i++ {
26 go exe(i, &wg)
27 }
28
29 fmt.Println("모든 goroutine이 끝날 때까지 대기합니다...")
30 wg.Wait()
31 fmt.Println("모든 작업이 완료되었습니다.")
32
33}
34
Goroutine Scheduler (M:N-model)
In het voorgaande deel over het M:N-model en de toewijzing en het beheer van OS-threads, zal ik nu dieper ingaan op het goroutine GMP-model.
- G (Goroutine): De kleinste werkeenheid die in Go wordt uitgevoerd.
- M (Machine): De OS-thread (de feitelijke uitvoeringslocatie).
- P (Processor): Het logische proces dat door de Go runtime wordt beheerd.
P heeft bovendien een lokale uitvoeringswachtrij (Local Run Queue) en fungeert als scheduler die toegewezen G's aan M toewijst. Kortom, een goroutine is:
Het operationele proces van GMP is als volgt:
- Wanneer een G (Goroutine) wordt gecreëerd, 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, preempted.
- Work-Stealing: Als de lokale uitvoeringswachtrij van een P leeg raakt, controleert een andere P de globale wachtrij. Als daar ook geen G (Goroutine) aanwezig is, 'steelt' deze taken van een andere lokale P (Processor) om ervoor te zorgen dat alle M's continu actief blijven.
- Systeemoproepverwerking (Blocking): Als een G (Goroutine) tijdens de uitvoering geblokkeerd raakt, komt M (Machine) in een wachtstatus terecht. 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 één G (Goroutine) lange tijd wordt gepreëmpt, krijgt een andere G (Goroutine) de kans om te worden uitgevoerd.
Golang's GC (Garbage Collector) draait ook bovenop Goroutine, waardoor geheugen parallel kan worden opgeschoond met minimale onderbreking van de applicatie-uitvoering (STW), wat resulteert in efficiënt gebruik van systeemresources.
Tot slot is Golang een van de sterke punten van de taal, en er zijn nog veel meer. Ik hoop dat veel ontwikkelaars van Go zullen genieten.
Dank u.