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
GOMAXPROCSsegí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:
- Egy új goroutine jön létre, és egy elérhető P helyi sorába kerül.
- Az P-hez kapcsolódó M kiválaszt egy goroutine-ot a helyi sorból, és végrehajtja.
- Ha a helyi sor üres, az M megpróbálja ellopni a goroutine-okat más P-ktől.
- 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.
- 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.
- 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.