Middle+
Vorbește despre planificarea goroutine-urilor în Go.
sobes.tech AI
Răspuns de la AI
Planificatorul de goroutines în Go utilizează modelul M:N, unde M goroutines sunt mapate pe N fire de sistem de operare. Schimbă eficient goroutines între fire, implementând multitasking fără preemțiune cu puncte de întrerupere (preemption points).
Componentele principale ale modelului GPM:
- G (Goroutine): Abstracție a unității de cod în execuție, fir ușor gestionat de planificatorul Go.
- P (Procesor): Procesor logic, reprezentând un context de coadă locală de goroutines și resurse precum mutex-uri și planificatorul. Numărul de P implicit este determinat de numărul de nuclee logice ale procesorului, dar poate fi modificat prin
GOMAXPROCS. - M (Mașină/Thread): Thread al sistemului de operare care execută codul. M este asociat cu P și execută goroutines din coada locală a P sau coada globală.
Mod de funcționare:
- Se creează o goroutine nouă și se plasează în coada locală a unui P disponibil.
- M, asociat cu P, ia o goroutine din coada locală și o execută.
- Dacă coada locală este goală, M încearcă să fure goroutines de la alte P.
- Dacă P se blochează (de exemplu, la un apel sistem), M asociat se detașează, și alt M se asociază cu acel P. M blocat poate fi reutilizat când apelul sistemului s-a încheiat.
- Dacă o goroutine se blochează pe un canal sau mutex, ea este plasată în coada de așteptare, iar planificatorul alege o altă goroutine pentru a fi executată pe același M.
- Planificatorul efectuează verificări periodice pentru a evacua goroutine-urile care rulează mult timp, permițând altor goroutine să obțină timp CPU.
Punctele de preemțiune apar în anumite locuri:
- Apeluri de funcții/methods.
- Buclă care conține apeluri de funcții.
- Alocări de memorie.
- Primitive de sincronizare (canale, mutex-uri).
Exemplu de planificare:
package main
import (
"fmt"
"runtime"
"time"
)
func worker(id int) {
fmt.Printf("Goroutine %d începe să lucreze\n", id)
time.Sleep(time.Second) // Simulare de lucru
fmt.Printf("Goroutine %d a terminat de lucrat\n", id)
}
func main() {
runtime.GOMAXPROCS(2) // Setează numărul de procesoare logice
for i := 1; i <= 5; i++ {
go worker(i) // Creează goroutine
}
time.Sleep(3 * time.Second) // Așteaptă finalizarea goroutine-urilor
fmt.Println("Goroutine principală s-a terminat")
}
În acest exemplu, 5 goroutine sunt planificate pe 2 procesoare logice. Planificatorul Go va distribui aceste goroutine între firele de sistem disponibile, folosind cozi locale și, posibil, globale, precum și mecanismul de "work stealing".
Avantajele acestei abordări:
- Scalabilitate: Utilizare eficientă a sistemelor multi-core.
- Costuri reduse: Goroutine ușoare comparativ cu firele OS.
- Ușurință în utilizare: Planificarea integrată în limbaj face programarea paralelă mai accesibilă.