Sobes.tech
Middle+

Meséljen a goroutine-ok ütemezéséről Go-ban.

sobes.tech MI

Válasz az MI-től

A goroutine ütemező Go-ban az M:N modellt használja, ahol M goroutine van hozzárendelve N operációs rendszer szálhoz. Hatékonyan váltogat goroutine-ok között a szálak között, megvalósítva a preemptív multitaskingot pontokon (preemption points).

A GPM modell fő összetevői:

  • G (Goroutine): A futtatandó kód egységének absztrakciója, könnyű szál, amit a Go ütemező kezel.
  • P (Processzor): Logikus processzor, amely egy helyi goroutine sor és erőforrásokat, például mutexeket és ütemezőt reprezentál. Az alapértelmezett P számát a processzor logikus magjainak száma határozza meg, de módosítható a GOMAXPROCS segítségével.
  • M (Gép/Szál): Az operációs rendszer szála, amely a kódot végrehajtja. M kapcsolódik P-hez, és a P helyi sorából vagy a globális sorból futtat goroutine-okat.

Működési mód:

  1. Egy új goroutine jön létre, és egy elérhető P helyi sorába kerül.
  2. Az P-hez kapcsolódó M kiválaszt egy goroutine-ot a helyi sorból, és végrehajtja.
  3. Ha a helyi sor üres, az M megpróbálja ellopni a goroutine-okat más P-ktől.
  4. Ha P blokkolódik (például rendszerhívás közben), a hozzá kapcsolódó M leválasztódik, és egy másik M kapcsolódik ehhez a P-hez. A blokkolt M újrahasznosítható, amikor a rendszerhívás befejeződik.
  5. Ha egy goroutine csatornán vagy mutexon blokkolódik, várólistára kerül, és az ütemező másik goroutine-t választ ugyanazon M-n, hogy fusson.
  6. Az ütemező időszakosan ellenőrzi, hogy hosszú ideje futó goroutine-okat el tudja-e szorítani, így más goroutine-ok is kapnak CPU-időt.

A preemption pontok bizonyos helyeken fordulnak elő:

  • Függvény- vagy metódushívások.
  • Olyan ciklusok, amelyek függvényhívásokat tartalmaznak.
  • Memóriafoglalások.
  • Szinkronizációs primitívek (csatornák, mutexek).

Ütemezési példa:

package main

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

func worker(id int) {
	fmt.Printf("Goroutine %d elkezdett dolgozni\n", id)
	time.Sleep(time.Second) // Munka szimulálása
	fmt.Printf("Goroutine %d befejezte a munkát\n", id)
}

func main() {
	runtime.GOMAXPROCS(2) // A logikai processzorok számának beállítása

	for i := 1; i <= 5; i++ {
		go worker(i) // Goroutine-ok létrehozása
	}

	time.Sleep(3 * time.Second) // Várakozás a goroutine-ok befejezésére
	fmt.Println("Fő goroutine befejeződött")
}

Ebben a példában 5 goroutine van ütemezve 2 logikus processzorra. A Go ütemező ezeket a goroutine-okat az elérhető OS szálak között osztja szét, helyi és esetleg globális sorokat, valamint a "work stealing" mechanizmust használva.

Az ilyen megközelítés előnyei:

  • Skálázhatóság: Hatékony kihasználása a többmagos rendszereknek.
  • Alacsony overhead: Könnyű goroutine-ok az OS szálakhoz képest.
  • Használhatóság: A beépített ütemezés a nyelvben könnyebbé teszi a párhuzamos programozást.