Aceasta este traducerea în română a unui articol publicat inițial în engleză pe Stackademic (Medium): The Go Show: Behind the Magic of Runtime.
Bine ai venit la „The Go Show”, în culisele runtime-ului Go. Încercăm să descâlcim câteva dintre misterele Go, un limbaj cunoscut deopotrivă pentru putere și pentru cât de ușor se folosește. Fie că scrii cod de ani de zile, fie că abia te apuci, ghidul ăsta te poartă prin modelul de concurență al limbajului.
Ai fost vreodată într-un parc de distracții în care toate atracțiile merg, cozile înaintează și nu calci niciodată într-o gumă de mestecat? Pare o poveste, dar nu e. În spatele râsetelor și al vatei de zahăr, o rețea întreagă de sisteme lucrează împreună ca ziua ta să fie de neuitat.
Ești gata să intri în culise? Ne uităm în sala mașinilor care face ca programele tale Go să meargă ca unse. Primul pe listă: ce înseamnă de fapt runtime-ul Go.
Gândește-te la un parc de distracții. Totul, de la roller coaster la tarabele cu gustări, e coordonat dintr-un centru de comandă. În lumea Go, acest centru se numește runtime. E creierul aplicației tale și se ocupă de lucrurile esențiale: gestionarea memoriei prin garbage collector, planificarea execuției prin scheduler și execuția concurentă prin goroutine.
Imaginează-ți o echipă de angajați harnici care strâng gunoiul din parc. Sunt eroii nevăzuți ai locului, iar în runtime-ul Go rolul lor îl are garbage collector-ul. El ține memoria curată, eliberând datele de care programul nu mai are nevoie, ca spațiul să poată fi refolosit.
Acum gândește-te la operatorul care hotărăște cu grijă când pornește și când se oprește fiecare atracție, ca totul să meargă fără sincope și în siguranță. În Go, rolul lui îl are scheduler-ul, care împarte timpul de procesor între goroutine.
Și, în sfârșit, angajații care se ocupă de roata mare, de floricele sau de jocuri. În Go, rolurile astea le joacă goroutine-urile: thread-uri ușoare, pe care le pornești și le oprești foarte ieftin. Nu sunt thread-uri ale sistemului de operare: runtime-ul le distribuie pe un număr mic de thread-uri reale.
Garbage collector-ul, scheduler-ul și goroutine-urile nu lucrează fiecare pe cont propriu. Fac parte din același ansamblu, coordonat de runtime.
Aici e partea interesantă: când compilezi codul Go, tot ce îi trebuie programului ca să funcționeze ajunge într-un singur fișier binar. Executabilul își poartă cu el propriul runtime, așa că programul e de sine stătător. Pe mașina unde rulează binarul nu trebuie să instalezi Go. E ca un parc de distracții portabil, pe care îl ții în buzunar.
Runtime-ul Go e un parc construit cu atenție, cu toate rotițele și mecanismele care fac atracțiile să meargă. Când compilezi un program, nu scrii doar linii de cod: construiești o lume mică, autonomă, pe care o poți lua cu tine oriunde, fără instalări suplimentare.
Dacă vrei să mergi mai departe, citește și copierea superficială vs. copierea profundă în Go.
Note adăugate la traducere; nu există în articolul original din 2023. Actualizate pentru Go 1.27, versiunea curentă la data publicării.
Analogia cu parcul rămâne valabilă, dar câteva lucruri s-au schimbat de atunci și merită știute.
Scheduler-ul, mai concret. Runtime-ul lucrează cu trei piese: G (goroutine), M (machine, un thread al sistemului de operare) și P (processor, un context logic de execuție). Numărul de P-uri e dat de GOMAXPROCS și spune câte goroutine rulează efectiv în paralel. Din Go 1.14 există preemption asincronă: scheduler-ul poate întrerupe și o goroutine prinsă într-un tight loop, o buclă fără apeluri de funcții, așa că una singură nu mai poate ține un procesor ocupat la nesfârșit.
GOMAXPROCS în containere. Până la Go 1.24, GOMAXPROCS era implicit egal cu numărul de procesoare ale mașinii, chiar dacă programul rula într-un container limitat la, să zicem, 2 procesoare. Din Go 1.25 runtime-ul ține cont pe Linux de limita de CPU din cgroup. Detaliile sunt în articolul oficial despre GOMAXPROCS în containere. Pentru aplicațiile care rulează în Docker sau Kubernetes, schimbarea contează.
Un garbage collector nou. Go 1.25 a introdus experimental un garbage collector nou, Green Tea, iar din Go 1.26 el e colectorul implicit. Nu trebuie să schimbi nimic în cod; dacă ai motive să revii la cel vechi, compilezi cu GOEXPERIMENT=nogreenteagc.
O limită de memorie. Pe lângă GOGC, din Go 1.19 există GOMEMLIMIT, cu care îi spui runtime-ului cât de multă memorie are voie să folosească. Pentru tuning, punctul de plecare e ghidul oficial al garbage collector-ului.
Garbage collector-ul găsește acum goroutine blocate definitiv. Din Go 1.27 există un profil nou, goroutineleak, în runtime/pprof și la endpoint-ul /debug/pprof/goroutineleak. Runtime-ul folosește chiar garbage collector-ul ca să găsească goroutine blocate pe un canal sau pe un mutex pe care nicio altă goroutine nu le mai poate debloca. Nu prinde toate cazurile, dar prinde o clasă mare de leak-uri, de exemplu goroutine-urile rămase blocate când o funcție iese prea devreme din bucla care citește de pe canal.
„Un singur binar” are o mică excepție. Dacă programul folosește cgo, binarul poate depinde de bibliotecile sistemului (glibc). Pe Linux, unele pachete standard, cum ar fi net, folosesc cgo implicit atunci când e disponibil. Pentru un binar complet static, de pus într-o imagine Docker scratch sau distroless, compilează cu CGO_ENABLED=0.
La DO IT MAGIC SOFTWARE scriem în Go sisteme care rulează în producție, de la platforme web la soluții AI locale. Dacă ai un proiect Go care are nevoie de o a doua opinie, scrie-ne.