Go
Parallelität
Suche…
Einführung
In Go wird Parallelität durch die Verwendung von Goroutinen erreicht, und die Kommunikation zwischen den Goroutinen erfolgt normalerweise über Kanäle. Es gibt jedoch auch andere Synchronisationsmittel wie Mutexe und Wartegruppen, die immer dann verwendet werden sollten, wenn sie bequemer als Kanäle sind.
Syntax
- go doWork () // führe die Funktion doWork als Goroutine aus
- ch: = make (chan int) // Neuen Kanal vom Typ int deklarieren
- ch <- 1 // Senden eines Kanals
- value = <-ch // Empfangen von einem Kanal
Bemerkungen
Goroutines in Go ähneln Threads in anderen Sprachen. Intern erstellt Go eine Anzahl von Threads (von GOMAXPROCS
) und plant die Ausführung der Goroutines auf den Threads. Aufgrund dieses Designs sind die Parallelitätsmechanismen von Go hinsichtlich Speicherauslastung und Initialisierungszeit viel effizienter als Threads.
Erstellen von Goroutinen
Jede Funktion kann als Goroutine aufgerufen werden, indem dem Aufruf go
das Schlüsselwort go
vorangestellt wird:
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
Beachten Sie, dass der Rückgabewert der Funktion ignoriert wird.
Hallo Welt Goroutine
Einzelkanal, Einzel-Goroutine, einmal schreiben, einmal lesen.
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)
}
Lass es auf dem Spielplatz laufen
Der Kanal ch
ist ein ungepufferter oder synchroner Kanal .
Die time.Sleep
dient hier zur Veranschaulichung der Funktion main()
, die auf den Kanal ch
wartet , was bedeutet, dass das als Goroutine ausgeführte Funktionsliteral Zeit hat, einen Wert über diesen Kanal zu senden: Der Empfangsoperator <-ch
blockiert die Ausführung von main()
. Wenn dies nicht der Fall ist, wird die Goroutine beim Beenden von main()
und hat keine Zeit, ihren Wert zu senden.
Warten auf Goroutinen
Go - Programme zu beenden , wenn die main
endet , deshalb ist es üblich, für alle goroutines zu warten zu beenden. Eine gängige Lösung hierfür ist die Verwendung eines sync.WaitGroup- Objekts.
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")
}
Führen Sie das Beispiel auf dem Spielplatz aus
WaitGroup-Verwendung in der Reihenfolge der Ausführung:
- Deklaration der globalen Variablen. Global zu machen ist der einfachste Weg, um es für alle Funktionen und Methoden sichtbar zu machen.
- Erhöhen Sie den Zähler. Dies muss in der Haupt goroutine erfolgen, da es keine Garantie gibt , dass ein neu gestartet goroutine vor 4 aufgrund Speichermodell ausgeführt wird garantiert .
- Zähler verringern. Dies muss am Ausgang einer Goroutine erfolgen. Durch die Verwendung eines zurückgestellten Anrufs stellen wir sicher, dass er immer dann aufgerufen wird, wenn die Funktion endet , unabhängig davon, wie sie endet.
- Warten, bis der Zähler 0 erreicht. Dies muss in der Haupt-Goroutine erfolgen, um zu verhindern, dass das Programm beendet wird, bevor alle Goroutinen abgeschlossen sind.
* Parameter werden ausgewertet, bevor eine neue Goroutine gestartet wird . Daher ist es notwendig, ihre Werte explizit vor wg.Add(10)
zu definieren, damit möglicherweise in Panik wg.Add(10)
Code den Zähler nicht erhöht. Hinzufügen von 10 Elementen zur WaitGroup, sodass auf 10 Elemente wg.Wait
wird, bevor wg.Wait
das Steuerelement wieder an main()
goroutine zurückgibt. Hier wird der Wert von i in der for-Schleife definiert.
Verwenden von Verschlüssen mit Goroutinen in einer Schleife
In einer Schleife ist die Schleifenvariable (val) im folgenden Beispiel eine einzelne Variable, deren Wert sich ändert, wenn sie über die Schleife geht. Daher muss man Folgendes tun, um jeden Wertwert tatsächlich an die Goroutine zu übergeben:
for val := range values {
go func(val interface{}) {
fmt.Println(val)
}(val)
}
Wenn Sie einfach go func(val interface{}) { ... }()
ausführen möchten, ohne val zu übergeben, ist der Wert von val
der Wert von val
, wenn die Goroutinen tatsächlich ausgeführt werden.
Ein anderer Weg, um den gleichen Effekt zu erzielen, ist:
for val := range values {
val := val
go func() {
fmt.Println(val)
}()
}
Das seltsam aussehende val := val
erstellt in jeder Iteration eine neue Variable, auf die die Goroutine zugreift.
Anhalten von Goroutinen
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 mit zwei Goroutinen
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)
}
}
Führen Sie eine geringfügig modifizierte Version dieses Codes in Go Playground aus