Go
Concorrenza
Ricerca…
introduzione
In Go, la concorrenza viene raggiunta attraverso l'uso di goroutine e la comunicazione tra le goroutine viene solitamente eseguita con i canali. Tuttavia, sono disponibili altri mezzi di sincronizzazione, come mutex e wait groups, che dovrebbero essere utilizzati ogni volta che sono più convenienti dei canali.
Sintassi
- go doWork () // esegue la funzione doWork come una goroutine
- ch: = make (chan int) // dichiara un nuovo canale di tipo int
- ch <- 1 // invio su un canale
- value = <-ch // ricezione da un canale
Osservazioni
Le goroutine in Go sono simili alle discussioni in altre lingue in termini di utilizzo. Internamente, Go crea un numero di thread (specificato da GOMAXPROCS
) e quindi pianifica le goroutine per l'esecuzione sui thread. Grazie a questo design, i meccanismi di concorrenza di Go sono molto più efficienti dei thread in termini di utilizzo della memoria e tempo di inizializzazione.
Creazione di goroutine
Qualsiasi funzione può essere invocata come una goroutine mediante il prefisso della sua chiamata con la parola chiave go
:
func DoMultiply(x,y int) {
// Simulate some hard work
time.Sleep(time.Second * 1)
fmt.Printf("Result: %d\n", x * y)
}
go DoMultiply(1,2) // first execution, non-blocking
go DoMultiply(3,4) // second execution, also non-blocking
// Results are printed after a single second only,
// not 2 seconds because they execute concurrently:
// Result: 2
// Result: 12
Si noti che il valore di ritorno della funzione è ignorato.
Ciao World Goroutine
canale singolo, goroutine singola, una scrittura, una lettura.
package main
import "fmt"
import "time"
func main() {
// create new channel of type string
ch := make(chan string)
// start new anonymous goroutine
go func() {
time.Sleep(time.Second)
// send "Hello World" to channel
ch <- "Hello World"
}()
// read from channel
msg, ok := <-ch
fmt.Printf("msg='%s', ok='%v'\n", msg, ok)
}
Il canale ch
è un canale senza buffer o sincrono .
Il time.Sleep
è qui per illustrare la funzione main()
aspetterà sul canale ch
, che significa che la funzione letterale eseguita come una goroutine ha il tempo di inviare un valore attraverso quel canale: l' operatore di ricezione <-ch
bloccherà l'esecuzione di main()
. Se così non fosse, la goroutine verrebbe uccisa quando main()
uscisse e non avrebbe il tempo di inviare il suo valore.
In attesa di goroutine
I programmi Go terminano quando termina la funzione main
, quindi è prassi comune aspettare che tutte le goroutine finiscano. Una soluzione comune per questo è utilizzare un oggetto sync.WaitGroup .
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup // 1
func routine(i int) {
defer wg.Done() // 3
fmt.Printf("routine %v finished\n", i)
}
func main() {
wg.Add(10) // 2
for i := 0; i < 10; i++ {
go routine(i) // *
}
wg.Wait() // 4
fmt.Println("main finished")
}
Esegui l'esempio nel parco giochi
Uso WaitGroup in ordine di esecuzione:
- Dichiarazione di variabile globale. Rendendolo globale è il modo più semplice per renderlo visibile a tutte le funzioni e i metodi.
- Aumentare il contatore. Questo deve essere fatto nella goroutine principale perché non vi è alcuna garanzia che una goroutine appena avviata venga eseguita prima delle 4 a causa delle garanzie del modello di memoria.
- Diminuendo il contatore. Questo deve essere fatto all'uscita di una goroutine. Usando una chiamata differita, ci assicuriamo che venga chiamata ogni volta che la funzione termina , indipendentemente da come finisce.
- Aspettando che il contatore raggiunga 0. Questo deve essere fatto nella goroutine principale per impedire che il programma esca prima che tutte le goroutine siano finite.
* I parametri vengono valutati prima di iniziare una nuova goroutine . Quindi è necessario definire i loro valori esplicitamente prima di wg.Add(10)
modo che il codice eventualmente panico non aumenti il contatore. Aggiungendo 10 elementi al WaitGroup, quindi attenderà 10 elementi prima che wg.Wait
restituisca il controllo alla goroutine main()
. Qui, il valore di i è definito nel ciclo for.
Usando chiusure con goroutine in un ciclo
Quando si trova in un ciclo, la variabile di ciclo (val) nell'esempio seguente è una variabile singola che cambia valore mentre passa sopra il ciclo. Pertanto si deve fare quanto segue per passare effettivamente ogni val di valori alla goroutine:
for val := range values {
go func(val interface{}) {
fmt.Println(val)
}(val)
}
Se dovessi fare solo go func(val interface{}) { ... }()
senza passare val, allora il valore di val
sarà qualunque cosa valga quando le goroutine vengono effettivamente eseguite.
Un altro modo per ottenere lo stesso effetto è:
for val := range values {
val := val
go func() {
fmt.Println(val)
}()
}
val := val
dall'aspetto strano val := val
crea una nuova variabile in ogni iterazione, a cui accede la goroutine.
Fermare le goroutine
package main
import (
"log"
"sync"
"time"
)
func main() {
// The WaitGroup lets the main goroutine wait for all other goroutines
// to terminate. However, this is no implicit in Go. The WaitGroup must
// be explicitely incremented prior to the execution of any goroutine
// (i.e. before the `go` keyword) and it must be decremented by calling
// wg.Done() at the end of every goroutine (typically via the `defer` keyword).
wg := sync.WaitGroup{}
// The stop channel is an unbuffered channel that is closed when the main
// thread wants all other goroutines to terminate (there is no way to
// interrupt another goroutine in Go). Each goroutine must multiplex its
// work with the stop channel to guarantee liveness.
stopCh := make(chan struct{})
for i := 0; i < 5; i++ {
// It is important that the WaitGroup is incremented before we start
// the goroutine (and not within the goroutine) because the scheduler
// makes no guarantee that the goroutine starts execution prior to
// the main goroutine calling wg.Wait().
wg.Add(1)
go func(i int, stopCh <-chan struct{}) {
// The defer keyword guarantees that the WaitGroup count is
// decremented when the goroutine exits.
defer wg.Done()
log.Printf("started goroutine %d", i)
select {
// Since we never send empty structs on this channel we can
// take the return of a receive on the channel to mean that the
// channel has been closed (recall that receive never blocks on
// closed channels).
case <-stopCh:
log.Printf("stopped goroutine %d", i)
}
}(i, stopCh)
}
time.Sleep(time.Second * 5)
close(stopCh)
log.Printf("stopping goroutines")
wg.Wait()
log.Printf("all goroutines stopped")
}
Ping pong con due goroutine
package main
import (
"fmt"
"time"
)
// The pinger prints a ping and waits for a pong
func pinger(pinger <-chan int, ponger chan<- int) {
for {
<-pinger
fmt.Println("ping")
time.Sleep(time.Second)
ponger <- 1
}
}
// The ponger prints a pong and waits for a ping
func ponger(pinger chan<- int, ponger <-chan int) {
for {
<-ponger
fmt.Println("pong")
time.Sleep(time.Second)
pinger <- 1
}
}
func main() {
ping := make(chan int)
pong := make(chan int)
go pinger(ping, pong)
go ponger(ping, pong)
// The main goroutine starts the ping/pong by sending into the ping channel
ping <- 1
for {
// Block the main thread until an interrupt
time.Sleep(time.Second)
}
}
Esegui una versione leggermente modificata di questo codice in Go Playground