Programarea paralelă - Gorutine
Gorutine
Gorutinele (goroutines) reprezintă operațiuni paralele care pot fi executate independent de funcția în care au fost lansate. Caracteristica principală a gorutinelor este că ele pot fi executate paralel. Așadar, pe arhitecturi multi-core, există posibilitatea de a rula gorutinele pe nuclee diferite ale procesorului, astfel încât gorutinele vor fi executate paralel și programul se va încheia mai rapid.
Fiecare gorutină reprezintă, de obicei, un apel de funcție și execută secvențial toate instrucțiunile sale. Când lansăm un program în Go, deja lucrăm cu cel puțin o gorutină, reprezentată de funcția main. Această funcție execută secvențial toate instrucțiunile care sunt definite în interiorul său.
Pentru a defini gorutine se utilizează operatorul go, care se pune înaintea apelului funcției:
go apel_functie
De exemplu, să definim mai multe gorutine care calculează factorialul unui număr:
package main
import "fmt"
func main() {
for i := 1; i < 7; i++{
go factorial(i)
}
fmt.Println("The End")
}
func factorial(n int){
if(n < 1){
fmt.Println("Număr invalid")
return
}
result := 1
for i := 1; i <= n; i++{
result *= i
}
fmt.Println(n, "-", result)
}
În ciclul for, sunt lansate succesiv șase gorutine prin apelul go factorial(i). Adică, practic, acesta este un apel obișnuit de funcție cu operatorul go.
Totuși, în loc de șase factoriale pe consolă, la apelarea programului putem vedea doar linia "The End":
The End
„Putem vedea” înseamnă că comportamentul programului în acest caz este nedeterministic. De exemplu, ieșirea poate arăta și astfel:
2 - 2
1 - 1
4 - 24
The End
5 - 120
De ce se întâmplă acest lucru? După apelul go factorial(i), funcția main lansează o gorutină care începe să ruleze în contextul său, independent de funcția main. Practic, main și factorial rulează paralel. Totuși, gorutina principală este apelul funcției main. Și dacă execuția acestei funcții se încheie, atunci întreaga aplicație se încheie.
Deoarece apelurile funcției factorial sunt gorutine, funcția main nu așteaptă finalizarea acestora și continuă să ruleze după ce le lansează. Unele gorutine pot termina înaintea funcției main, iar prin urmare, vom putea vedea rezultatele acestora pe consolă.
Dar poate apărea situația în care funcția main se termină înainte ca apelurile funcției factorial să fie completate. De aceea, este posibil să nu vedem pe consolă factorialele numerelor.
Pentru a vedea totuși rezultatul execuției gorutinelor, să punem un apel al funcției fmt.Scanln() la sfârșitul funcției main, care va aștepta input de la utilizator din consolă:
package main
import "fmt"
func main() {
for i := 1; i < 7; i++{
go factorial(i)
}
fmt.Scanln() // așteptăm input din partea utilizatorului
fmt.Println("The End")
}
func factorial(n int){
if(n < 1){
fmt.Println("Număr invalid")
return
}
result := 1
for i := 1; i <= n; i++{
result *= i
}
fmt.Println(n, "-", result)
}
Acum vom putea vedea rezultatele tuturor apelurilor funcției factorial:
1 - 1
3 - 6
5 - 120
4 - 24
2 - 2
6 - 720
The End
Este important de menționat că, deoarece fiecare gorutină este lansată în propriul său context și se execută independent și paralel față de celelalte gorutine, nu putem determina cu exactitate care gorutină se va termina prima. De exemplu, gorutina go factorial(2) este lansată înaintea go factorial(5), dar poate termina mai târziu.
Gorutinele pot reprezenta și apeluri ale funcțiilor anonime:
package main
import "fmt"
func main() {
for i := 1; i < 7; i++{
go func(n int){
result := 1
for j := 1; j <= n; j++{
result *= j
}
fmt.Println(n, "-", result)
}(i)
}
fmt.Scanln()
fmt.Println("The End")
}