---
id: 208cfd8127284f07337b5f5002f3351d
author: hamori
title: 'Go-gorutinenes (Goroutine) virkemåte: 2KB variabel stakk og M:N GMP-skeduleren'
description: Den forklarer forskjellen mellom samtidighet (concurrency) og parallellisme (parallelism), fra goroutinenes initielle, dynamisk voksende stack på 2 KB, via brukernivå kontekstbytte, til GOMAXPROCS og GMP-modellen (Goroutine-Machine-Processor) for kjøretidsplanlegging.
language: "no"
date: 2025-08-22T13:28:15.766056383Z
path: /blog/posts/goroutine-basics-z3fbe209e
category: concurrency
---

# Goroutine

Når Gopher-utviklere blir bedt om å fortelle om fordelene med Golang, dukker det ofte opp artikler om **samtidighet (Concurrency)**. Grunnlaget for disse er **goroutine (goroutine)**, som kan håndteres lett og enkelt. Her har vi skrevet en kort oversikt over dette.

### **Samtidighet (Concurrency) vs parallellitet (Parallelism)**

Før vi forstår goroutine, ønsker vi å gjennomgå to begreper som ofte forveksles.

- Samtidighet: Samtidighet handler om å håndtere mange oppgaver samtidig. Det betyr ikke nødvendigvis at de faktisk kjøres samtidig, men er et strukturelt og logisk konsept der flere oppgaver deles opp i små enheter og utføres på skiftende måte, slik at det for brukeren ser ut som om flere oppgaver behandles samtidig. Samtidighet er også mulig på en enkeltkjerne.
- Parallellitet: Parallellitet betyr «å behandle flere oppgaver samtidig på flere kjerner». Bokstavelig talt utføres oppgaver parallelt, og andre arbeidsoppgaver kjøres samtidig.

Goroutine gjør det enkelt å implementere samtidighet gjennom Go-runtimens skjemalegger, og `GOMAXPROCS`-innstillingen gjør at parallellitet også utnyttes naturlig.

Den mye brukte multitråding (Multi thread) i Java er et typisk eksempel på parallellitet.

# Hvorfor er goroutine så god?

### Lettvekt (lightweight)

Kostnaden for å opprette en goroutine er svært lav sammenlignet med andre språk. Man kan spørre seg hvorfor Golang bruker så lite, og svaret er at opprettelsen håndteres innenfor Go-runtimen. Dette skyldes at det er en lettvekts logisk tråd, mindre enn en OS-tråd, med en startstack på omtrent 2 KB som dynamisk kan utvides etter behov i implementeringen.

Fordi stacken håndteres i enheter, er opprettelse og fjerning svært raske og billige, og det er mulig å kjøre flere millioner goroutiner uten at det belaster systemet nevneverdig. Dette gjør at goroutine, takket være runtime-skjemalegggeren, kan minimere innblanding fra OS-kjernen.

### Ytelse (performance)

For det første medfører goroutine lite innblanding fra OS-kjernen, slik som beskrevet ovenfor. Kontekstbytte på brukernivå (User-Level) er derfor billigere enn kontekstbytte mellom OS-tråder, og oppgaver kan byttes raskt.

I tillegg bruker den en M:N-modell der goroutiner tildeles OS-tråder og administreres. Ved å opprette en pool av OS-tråder trenger man ikke mange tråder, og det er mulig å behandle oppgaver med få tråder. For eksempel, når en systemkall eller en annen venting oppstår, kan Go-runtimen kjøre en annen goroutine på OS-tråden, slik at OS-tråden ikke står stille og CPU-en utnyttes effektivt, noe som gir rask behandling.

Dette gjør at Golang kan oppnå høyere ytelse enn andre språk, særlig for I/O-operasjoner.

### Konsis (concise)

At man kan gjøre en funksjon samtidig med kun nøkkelordet `go` er også en stor fordel.

Man må ellers bruke komplekse låsemekanismer som `Mutex` og `Semaphore`, og når man bruker låser, må man nødvendigvis ta hensyn til deadlock-tilstander (DeadLock), noe som gjør designfasen mer kompleks allerede før utvikling starter.

Goroutine følger filosofien «Ikke del minne for å kommunisere, kommuniser for å dele minne», og anbefaler dataoverføring gjennom `kanal (Channel)`. `SELECT` kombineres med kanaler og støtter også behandling av den kanalen som først har data klar. Med `sync.WaitGroup` kan man enkelt vente til alle goroutiner er ferdige, og dermed håndtere arbeidsflyten på en enkel måte. Takket være disse verktøyene kan man forhindre datarace-problemer mellom tråder og håndtere samtidighet tryggere.

Med `context` kan man dessuten styre livssyklus, kansellering, tidsavbrudd, frister og forespørselsomfang på brukernivå (User-Level), noe som gir en viss grad av stabilitet.

# Parallelt arbeid i Goroutine (GOMAXPROCS)

Etter å ha nevnt fordelene med goroutine-samtidighet, kan man undre seg over om parallellitet ikke støttes. I dag har CPU-er et antall kjerner som har passert ti, og også vanlige hjemme-PC-er har ganske mange kjerner.

Goroutine utfører imidlertid også parallelt arbeid, og det gjøres med `GOMAXPROCS`.

Hvis `GOMAXPROCS` ikke settes, blir verdien satt forskjellig avhengig av versjon.

1. Før 1.5: Standardverdien er 1. Hvis man trenger mer enn 1, må det settes eksplisitt, for eksempel med `runtime.GOMAXPOCS(runtime.NumCPU())`.
2. 1.5 til 1.24: Verdien endret seg til antall tilgjengelige logiske kjerner. Fra denne versjonen er det vanligvis ikke nødvendig for utvikleren å sette den, med mindre det finnes særlige begrensninger.
3. 1.25: Som et språk kjent for containermiljøer, sjekker det nå cGroup i Linux for å finne `CPU-begrensningen` som er satt for containeren.

   Dersom antall logiske kjerner er 10 og CPU-begrensningen er 5, settes `GOMAXPROCS` til det lavere tallet, 5.

Endringen i 1.25 er en svært stor endring, fordi bruken av språket i containermiljøer har økt. Dermed kan unødvendig opprettelse av tråder og kontekstbytte reduseres, og CPU-trottling kan forhindres.

```go
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func exe(name int, wg *sync.WaitGroup) {
	defer wg.Done()

	fmt.Printf("Goroutine %d: 시작\n", name) // Goroutine %d: starter
	time.Sleep(10 * time.Millisecond) // Forsinkelse for å simulere arbeid
	fmt.Printf("Goroutine %d: 시작\n", name) // Goroutine %d: starter
}

func main() {
	runtime.GOMAXPROCS(2) // Bruker kun 2 CPU-kjerner
	wg := sync.WaitGroup();
  goroutineCount := 10
	wg.Add(goroutineCount)

	for i := 0; i < goroutineCount; i++ {
		go exe(i, &wg)
	}

	fmt.Println("Venter til alle goroutiner er ferdige...")
	wg.Wait()
	fmt.Println("Alle oppgaver er fullført.")

}

```

# Skjemalegger for Goroutine (M:N-modell)

Det som ble nevnt tidligere, at goroutiner tildeles OS-tråder og administreres med M:N-modellen, kan utdypes med GMP-modellen for goroutine.

- G (Goroutine): Den minste arbeidsenheten som kjøres i Go
- M (Machine): OS-tråd (det faktiske utførelsesstedet)
- P (Processor): Den logiske prosessen som administreres av Go-runtimen

P har i tillegg en lokal kjøreko (Local Run Queue), og fungerer som skjemalegger ved å tildele G til M. Enkelt sagt fungerer goroutine slik:

Arbeidsgangen i GMP er som følger:

1. Når en G (Goroutine) opprettes, tildeles den til P (Processor)s lokale kjøreko.
2. P (Processor) tildeler G (Goroutine) i den lokale kjøreko til M (Machine).
3. M (Machine) returnerer tilstanden til G (Goroutine), som kan være block, complete eller preempted.
4. Work-Stealing (arbeidstyveri): Dersom den lokale kjøreko til en P blir tom, sjekker en annen P den globale køen. Finnes det heller ingen G der, stjeles arbeid fra en annen lokal P, slik at alle M-er fortsetter å arbeide uten pause.
5. Behandling av systemkall (Blocking): Når en G (Goroutine) blokkeres under kjøring, går M (Machine) i venstilling. P (Processor) skilles da fra den blokkerte M (Machine), kobles til en annen M (Machine) og kjører neste G (Goroutine). Dermed oppstår det ingen CPU-sløsing selv under venting på I/O-operasjoner.
6. Dersom en G (Goroutine) bruker tiden sin over lang tid (preempted), får en annen G (Goroutine) sjansen til å kjøre.

Golang kjører også søppelinnsamling (GC, Garbage Collector) på goroutiner. Den rydder minnet parallelt, samtidig som avbruddet av applikasjonen holdes til et minimum (STW), og dermed utnyttes systemressursene effektivt.

Til slutt er Golang et av språkets sterke fortrinn, og det finnes mange flere. Jeg håper mange utviklere får glede av å bruke Go.

Takk skal dere ha.
