Sobes.tech
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:

  1. Se creează o goroutine nouă și se plasează în coada locală a unui P disponibil.
  2. M, asociat cu P, ia o goroutine din coada locală și o execută.
  3. Dacă coada locală este goală, M încearcă să fure goroutines de la alte P.
  4. 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.
  5. 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.
  6. 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ă.