Sobes.tech
Middle+

Vertel over de planning van goroutines in Go.

sobes.tech AI

Antwoord van AI

De scheduler voor goroutines in Go gebruikt het M:N-model, waarbij M goroutines worden gekoppeld aan N besturingssysteemthreads. Het schakelt efficiënt tussen goroutines op threads, en implementeert multitasking zonder preëmptie met pauzepunten (preemption points).

Belangrijkste componenten van het GPM-model:

  • G (Goroutine): Abstractie van de uit te voeren code-eenheid, lichtgewicht thread, beheerd door de Go scheduler.
  • P (Processor): Logische processor, die een context van lokale wachtrij van goroutines en bronnen zoals mutexes en scheduler vertegenwoordigt. Het standaard aantal P wordt bepaald door het aantal logische cores van de processor, maar kan worden aangepast via GOMAXPROCS.
  • M (Machine/Thread): Besturingssysteemthread die de code uitvoert. M is verbonden met P en voert goroutines uit de lokale P-wachtrij of de globale wachtrij uit.

Werking:

  1. Een nieuwe goroutine wordt gemaakt en in de lokale wachtrij van een beschikbare P geplaatst.
  2. M, verbonden met P, pakt een goroutine uit de lokale wachtrij en voert deze uit.
  3. Als de lokale wachtrij leeg is, probeert M goroutines te stelen van andere P's.
  4. Als P geblokkeerd raakt (bijvoorbeeld bij een systeemoproep), wordt de bijbehorende M losgekoppeld, en een andere M wordt verbonden met die P. Geblokkeerde M kan opnieuw worden gebruikt wanneer de systeemoproep is voltooid.
  5. Als een goroutine blokkeert op een kanaal of mutex, wordt deze in de wachtrij voor wachten geplaatst, en de scheduler kiest een andere goroutine om uit te voeren op dezelfde M.
  6. De scheduler voert periodieke controles uit om langdurig werkende goroutines te verdrijven, zodat andere goroutines CPU-tijd kunnen krijgen.

Preemption points treden op op specifieke plaatsen:

  • Functie- of methodenaanroepen.
  • Lussen die functieaanroepen bevatten.
  • Geheugenallocaties.
  • Synchronisatie-primitieven (kanalen, mutexes).

Voorbeeld van planning:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func worker(id int) {
	fmt.Printf("Goroutine %d begint te werken\n", id)
	time.Sleep(time.Second) // Werk simuleren
	fmt.Printf("Goroutine %d heeft gewerkt\n", id)
}

func main() {
	runtime.GOMAXPROCS(2) // Stel het aantal logische cores in

	for i := 1; i <= 5; i++ {
		go worker(i) // Goroutines maken
	}

	time.Sleep(3 * time.Second) // Wachten tot alle goroutines klaar zijn
	fmt.Println("Hoofdgoroutine is klaar")
}

In dit voorbeeld worden 5 goroutines gepland op 2 logische cores. De Go scheduler verdeelt deze goroutines over de beschikbare OS-threads, gebruikmakend van lokale en mogelijk globale wachtrijen, en het "work stealing" mechanisme.

Voordelen van deze aanpak:

  • Schaalbaarheid: Efficiënt gebruik van multicore-systemen.
  • Lage overhead: Lichtgewicht goroutines in vergelijking met OS-threads.
  • Gebruiksgemak: De ingebouwde planning in de taal maakt parallel programmeren toegankelijker.