Sobes.tech
Middle+

Erzählen Sie von der Planung von Goroutinen in Go.

sobes.tech KI

Antwort von AI

Der Scheduler für Goroutines in Go verwendet das M:N-Modell, bei dem M Goroutines auf N Betriebssystem-Threads abgebildet werden. Es wechselt effizient zwischen Goroutines auf Threads und implementiert Multitasking ohne Preemption mit Haltepunkten (Preemption Points).

Hauptkomponenten des GPM-Modells:

  • G (Goroutine): Abstraktion der auszuführenden Codeeinheit, leichter Thread, verwaltet vom Go-Scheduler.
  • P (Prozessor): Logischer Prozessor, der einen Kontext einer lokalen Warteschlange von Goroutines und Ressourcen wie Mutexes und Scheduler repräsentiert. Die Anzahl der P wird standardmäßig durch die Anzahl der logischen Kerne des Prozessors bestimmt, kann aber durch GOMAXPROCS geändert werden.
  • M (Machine/Thread): Betriebssystem-Thread, der Code ausführt. M ist mit P verbunden und führt Goroutines aus der lokalen P-Warteschlange oder der globalen Warteschlange aus.

Funktionsweise:

  1. Eine neue Goroutine wird erstellt und in die lokale Warteschlange eines verfügbaren P gelegt.
  2. M, das mit P verbunden ist, nimmt eine Goroutine aus der lokalen Warteschlange und führt sie aus.
  3. Wenn die lokale Warteschlange leer ist, versucht M, Goroutines von anderen P zu stehlen.
  4. Wenn P blockiert (z.B. bei einem Systemaufruf), trennt sich das zugehörige M ab, und ein anderes M verbindet sich mit diesem P. Das blockierte M kann wiederverwendet werden, wenn der Systemaufruf beendet ist.
  5. Wenn eine Goroutine auf einem Kanal oder Mutex blockiert, wird sie in die Warteschlange für Wartende gestellt, und der Scheduler wählt eine andere Goroutine zur Ausführung auf demselben M.
  6. Der Scheduler führt periodische Überprüfungen durch, um lang laufende Goroutines zu verdrängen, sodass andere Goroutines CPU-Zeit erhalten.

Preemption Points treten an bestimmten Stellen auf:

  • Funktions- und Methodenaus calls.
  • Schleifen, die Funktionsaufrufe enthalten.
  • Speicherzuweisungen.
  • Synchronisationsprimitive (Kanäle, Mutexes).

Beispiel für Scheduling:

package main

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

func worker(id int) {
	fmt.Printf("Goroutine %d beginnt zu arbeiten\n", id)
	time.Sleep(time.Second) // Arbeit simulieren
	fmt.Printf("Goroutine %d hat die Arbeit beendet\n", id)
}

func main() {
	runtime.GOMAXPROCS(2) // Anzahl der logischen Prozessoren festlegen

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

	time.Sleep(3 * time.Second) // Warten auf Abschluss der Goroutines
	fmt.Println("Haupt-Goroutine beendet")
}

In diesem Beispiel werden 5 Goroutines auf 2 logische Prozessoren geplant. Der Go-Scheduler verteilt diese Goroutines auf die verfügbaren Betriebssystem-Threads, nutzt lokale und möglicherweise globale Warteschlangen sowie den "Work Stealing"-Mechanismus.

Vorteile dieses Ansatzes:

  • Skalierbarkeit: Effiziente Nutzung von Mehrkernsystemen.
  • Geringer Overhead: Leichte Goroutines im Vergleich zu OS-Threads.
  • Benutzerfreundlichkeit: Die integrierte Planung im Sprachkern macht parallele Programmierung zugänglicher.