by Daniele Maccioni
https://github.com/GendoIkari/golab-2015
La realtà è intrinsecamente parallela, multicore, multiutente.
Risolvere problemi simultanei su macchine "seriali" è complicato, controintuitivo.
L'applicazione tipica oggi: webapp.
Milioni di utenti in contemporanea, distribuita su multipli computer, servizi, infrastrutture, per essere presente su device di ogni tipo.
Gli strumenti "classici" che abbiamo sono nati in un ambiente più simile ad un Commodore 64 che ad una webapp.
package main
func doCalc1() {
// 2 seconds spent here...
}
func doCalc2() {
// 2 seconds spent here...
}
func doCalc3() {
// 2 seconds spent here...
}
func main() {
// ...
// insert long program here...
// ...
// We are executing three functions serially, like usual.
// Time = 6 seconds.
doCalc1()
doCalc2()
doCalc3()
// ...
// insert rest of the program here...
// ...
}
doCalc1()
doCalc2()
doCalc3()
2s
2s
2s
t
package main
func doCalc1() {
// 2 seconds spent here
}
func doCalc2() {
// 2 seconds spent here
}
func doCalc3() {
// 2 seconds spent here
}
func main() {
// ...
// insert long program here...
// ...
// We are executing three functions serially, like usual.
// Time = 6 seconds.
go doCalc1()
go doCalc2()
go doCalc3()
// ...
// insert rest of the program here...
// ...
}
doCalc1()
doCalc2()
doCalc3()
2s
2s
2s
t
(single core)
func getData() {
// 2 seconds of network delay
}
func doSlimCalc() {
// 2 seconds spent here
}
func doFatCalc() {
// 4 seconds spent here
}
Introduciamo un caso un po' più reale.
getData()
doSlimCalc()
doFatCalc()
~0s
1s
2s
t
(single core)
~0s
2s
1s
2s
doSlimCalc()
getData()
doFatCalc()
getData()
doSlimCalc()
doFatCalc()
~0s
1s
2s
t
(multi core)
~0s
2s
1s
2s
doSlimCalc()
getData()
doFatCalc()
doSlimCalc()
doFatCalc()
Canali per mandare e ricevere dati, a prova di concurrency.
ch1 := make(chan int)
ch1 <- 1
<-ch1
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
go func() {
ch1 <- "Hello"
ch1 <- "World!"
}()
go func() {
for {
fmt.Println(<-ch1)
}
}()
time.Sleep(1 * time.Second)
}
Hello
World!
sharedInput := 0
sharedOutput := 0
go func() {
for {
// ...
sharedInput = rand.Int()
// ...
}
}()
go func() {
for {
// ...
sharedOutput = sharedInput * 2
// ...
}
}()
inputs := make(chan int)
outputs := make(chan int)
go func() {
for {
// ...
inputs <- rand.Int()
// ...
}
}()
go func() {
for {
// ...
input := <-inputs
outputs <- input * 2
// ...
}
}()
Lettura e scrittura su un canale è bloccante, se non è bufferizzato.
I channel bufferizzato possono contenere n dati prima di diventare bloccanti.
ch := make(chan int, 10)
channel := make(chan int, 10)
go func() {
for i := 0; i < 10; i++ {
channel <- i
time.Sleep(100 * time.Millisecond)
}
}()
time.Sleep(500 * time.Millisecond)
for i := 0; i < 5; i++ {
<-channel
}
time.Sleep(500 * time.Millisecond)
for i := 0; i < 5; i++ {
<-channel
}
real 0m1.001s
user 0m0.000s
sys 0m0.000s
func addData() {
data := make(chan int)
go func() {
// get data from internet
// ...
data <- downloadNumber()
}
go func() {
// get data from user
// ...
data <- inputNumber()
}
x := <-data
y := <-data
return x + y
}
func addData() {
data := make(chan int)
go func() {
for i := 0; i < 100; i++ {
data <- i
}
close(data)
}()
sum := 0
for i := range data {
sum += i
}
return sum
}
I channel si possono chiudere per segnalare la fine della comunicazione.
La range su un channel estrae dati fino alla sua chiusura.
func producerSlow(output chan string) {
for {
output <- "Message!"
time.Sleep(1 * time.Second)
}
}
func producerFast(output chan int) {
for {
output <- rand.Int()
time.Sleep(500 * time.Millisecond)
}
}
func main() {
ch1 := make(chan string)
ch2 := make(chan int)
go producerSlow(ch1)
go producerFast(ch2)
for {
fmt.Println(<-ch1)
fmt.Println(<-ch2)
}
}
Message!
5577006791947779410
Message!
8674665223082153551
Message!
6129484611666145821
Message!
4037200794235010051
func main() {
ch1 := make(chan string)
ch2 := make(chan int)
go producerSlow(ch1)
go producerFast(ch2)
for {
select {
case msg := <-ch1:
fmt.Println(msg)
case num := <-ch2:
fmt.Println(num)
}
}
}
Message!
3916589616287113937
6334824724549167320
Message!
605394647632969758
1443635317331776148
func main() {
ch1 := make(chan string)
ch2 := make(chan int)
go producerSlow(ch1)
go producerFast(ch2)
for {
select {
case msg := <-ch1:
fmt.Println(msg)
case num := <-ch2:
fmt.Println(num)
case <-time.After(250 * time.Millisecond):
fmt.Println("Timeout!")
}
}
}
Message!
5577006791947779410
Timeout!
Timeout!
8674665223082153551
Timeout!
Message!
6129484611666145821
func After(d Duration) <-chan Time
La select ha la possibilità di avere un caso di default, eseguito nel caso tutti gli altri siano bloccati.
La presenza di un default rende di fatto la select non bloccante.
select {
case <- channel1:
// ...
case <- channel2:
// ...
case <- channel3:
// ...
case default:
// ...
}
func main() {
ch1 := make(chan string)
ch2 := make(chan int)
go producerSlow(ch1)
go producerFast(ch2)
for {
select {
case msg := <-ch1:
fmt.Println(msg)
case num := <-ch2:
fmt.Println(num)
default:
fmt.Println("Missing data!")
time.Sleep(250 * time.Millisecond)
}
}
}
Missing data!
5577006791947779410
Message!
Missing data!
Missing data!
8674665223082153551
Missing data!
Missing data!
Message!
6129484611666145821
Missing data!
Missing data!
4037200794235010051
Go offre una prospettiva sulla concurrency solida, efficace, semplice.
Goroutine, channel e select sono primitive di linguaggio facili da usare e da capire, utili a costruire strumenti più sofisticati.
L'approccio di Go rende veramente accessibile il design di applicazioni asincrone e parallele a tutti.