Go
samtidighet
Sök…
Introduktion
I Go uppnås samtidighet genom användning av goroutiner, och kommunikation mellan goroutiner sker vanligtvis med kanaler. Men andra sätt att synkronisera, som mutexer och väntegrupper, finns tillgängliga och bör användas när de är mer praktiska än kanaler.
Syntax
- go doWork () // kör funktionen doWork som en goroutine
- ch: = make (chan int) // förklara ny kanal av typen int
- ch <- 1 // skicka på en kanal
- värde = <-ch // mottagande från en kanal
Anmärkningar
Goroutiner i Go liknar trådar på andra språk vad gäller användning. Internt skapar Go ett antal trådar (specificeras av GOMAXPROCS
) och planerar sedan goroutinerna att köras på trådarna. På grund av denna design är Gos samtidighetsmekanismer mycket effektivare än trådar när det gäller minnesanvändning och initialiseringstid.
Skapa goroutiner
Varje funktion kan åberopas som en goroutine genom att prefixera sin åkallelse med nyckelordet 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
Observera att funktionens returvärde ignoreras.
Hej värld Goroutine
singelkanal, enda goroutin, en skrivning, en läst.
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)
}
Kanal ch
är en obuffrad eller synkron kanal .
time.Sleep
är här för att illustrera main()
-funktionen kommer att vänta på ch
kanalen, vilket innebär att funktionen bokstavligen exekverad som en goroutine har tid att skicka ett värde genom den kanalen: mottagningsoperatören <-ch
blockerar exekveringen av main()
. Om det inte gjorde det, skulle goroutinen dödas när main()
går ut och skulle inte ha tid att skicka sitt värde.
Väntar på goroutiner
Go program avslutas när main
, därför är det vanligt att vänta på att alla goroutines till slut. En vanlig lösning för detta är att använda ett sync.WaitGroup- objekt.
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")
}
Vänta gruppanvändning i ordningsföljd:
- Förklaring av den globala variabeln. Att göra det globalt är det enklaste sättet att synliggöra alla funktioner och metoder.
- Öka räknaren. Detta måste göras i huvud goroutine eftersom det inte finns någon garanti för att ett nystartat goroutine kommer att utföra före fyra på grund av minnesmodellgarantier .
- Minska räknaren. Detta måste göras vid utgången av en goroutin. Genom att använda ett uppskjutet samtal ser vi till att det kommer att ringas när funktionen slutar , oavsett hur den slutar.
- Väntar på att räknaren når 0. Detta måste göras i huvudgoroutinen för att förhindra att programmet går ut innan alla goroutiner har slutat.
* Parametrar utvärderas innan en ny goroutin startas . Därför är det nödvändigt att definiera deras värden uttryckligen före wg.Add(10)
så att eventuellt panikeringskod inte kommer att öka räknaren. Lägga till 10 objekt i WaitGroup, så det kommer att vänta i 10 artiklar innan wg.Wait
returnerar kontrollen tillbaka till main()
goroutine. Här definieras värdet på i i slingan för.
Använd stängningar med goroutiner i en slinga
I en slinga är slingvariabeln (val) i följande exempel en enda variabel som ändrar värde när den går över slingan. Därför måste man göra följande för att faktiskt överföra varje värdeval till goroutinen:
for val := range values {
go func(val interface{}) {
fmt.Println(val)
}(val)
}
Om du skulle göra just gå func(val interface{}) { ... }()
utan att passera val, då värdet på val
kommer att vara vad val är när goroutines faktiskt körs.
Ett annat sätt att få samma effekt är:
for val := range values {
val := val
go func() {
fmt.Println(val)
}()
}
Den konstigt snygga val := val
skapar en ny variabel i varje iteration, som sedan nås av goroutinen.
Stoppa goroutiner
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 med två goroutiner
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)
}
}
Kör en något modifierad version av denna kod i Go Playground