Der Channel ist ein Kerntyp in Go. Man kann ihn sich als eine Pipe vorstellen, durch die nebenläufige Kerneinheiten Daten zur Kommunikation senden oder empfangen können.
Sein Operator ist ein Pfeil<- 。
ch <- v // 发送值v到Channel ch中 v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
(Die Richtung des Pfeils gibt den Datenfluss an)
Genau wie bei den Datentypen map und slice muss ein Channel erstellt werden, bevor er verwendet werden kann:
ch := make(chan int)
Kanaltyp
Das Format zum Definieren eines Channel-Typs ist wie folgt:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
Es umfasst drei Arten von Definitionen. Das optionale<-gibt die Richtung des Channels an. Wenn keine Richtung angegeben ist, ist der Channel bidirektional und kann sowohl Daten empfangen als auch senden.
chan T // 可以接收和发送类型为 T 的数据 chan<- float64 // 只可以用来发送 float64 类型的数据 <-chan int // 只可以用来接收 int 类型的数据
<-Er bindet immer zuerst an den am weitesten links stehenden Typ.
chan<- chan int // 等价 chan<- (chan int) chan<- <-chan int // 等价 chan<- (<-chan int) <-chan <-chan int // 等价 <-chan (<-chan int) chan (<-chan int)
NutzungmakeInitialisiere einen Channel, und du kannst seine Kapazität festlegen:
make(chan int, 100)
Die Kapazität repräsentiert die maximale Anzahl von Elementen, die der Channel aufnehmen kann, und gibt die Größe des Puffers des Channels an.
Wenn keine Kapazität festgelegt ist oder die Kapazität auf 0 gesetzt ist, bedeutet das, dass der Channel keinen Puffer hat und die Kommunikation nur (blockierend) erfolgt, wenn sowohl Sender als auch Empfänger bereit sind. Wenn ein Puffer festgelegt ist, kann eine Blockierung ausbleiben: Senden blockiert erst, nachdem der Puffer voll ist, und Empfangen blockiert erst, nachdem der Puffer leer ist. Ein nil-Channel kommuniziert niemals.
Die eingebautecloseDie Methode kann den Channel schließen.
Du kannst mehrere Goroutines Daten von einem Channel empfangen bzw. an einen Channel senden lassen, ohne dass zusätzliche Synchronisationsmaßnahmen erforderlich sind.
Ein Channel kann als First-In-First-Out-(FIFO)-Warteschlange fungieren, wobei die Reihenfolge der empfangenen Daten mit der Reihenfolge der gesendeten Daten übereinstimmt.
Der Empfang des Channels unterstütztMehrfachzuweisung, wie zum Beispiel
v, ok := <-ch
Es kann verwendet werden, um zu prüfen, ob der Channel geschlossen wurde.
- send-Anweisung
Die send-Anweisung wird verwendet, um Daten an einen Channel zu senden, z. B.ch <- 3。
Die Definition lautet wie folgt:
SendStmt = Channel "<-" Expression . Channel = Expression .
Bevor die Kommunikation beginnt, müssen der Channel und der Ausdruck zuerst ausgewertet werden. Zum Beispiel wird im Folgenden (3+4) zuerst zu 7 ausgewertet und dann an den Channel gesendet.
c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)
Bevor das Senden fortfahren kann, ist die Kommunikation blockiert. Wie bereits erwähnt, wird bei einem ungepufferten Channel das Senden erst ausgeführt, nachdem der Empfänger bereit ist. Wenn ein Puffer vorhanden und nicht voll ist, wird das Senden ausgeführt.
Das Senden von Daten an einen bereits geschlossenen Channel verursachteine Laufzeit-Panik。
Das Senden von Daten an einen nil-Channel wird immer blockiert.
- Empfangsoperator
<-chEr wird verwendet, um Daten vom Channel ch zu empfangen. Dieser Ausdruck wird blockiert, bis Daten zum Empfangen vorhanden sind.
Das Empfangen von Daten aus einem nil-Channel wird immer blockiert.
Das Empfangen von Daten aus einem geschlossenen Channel wird nicht blockiert; es kehrt sofort zurück. Nachdem alle gesendeten Daten empfangen wurden, wird der Nullwert des Elementtyps zurückgegeben.
Wie bereits erwähnt, können Sie einen zusätzlichen Rückgabeparameter verwenden, um zu prüfen, ob der Channel geschlossen ist.
x, ok := <-ch x, ok = <-ch var x, ok = <-ch
Wenn OK false ist, zeigt dies an, dass das empfangene x ein erzeugter Nullwert ist, und dieser Channel wurde geschlossen oder ist leer.
blockierend
Standardmäßig bleiben Senden und Empfangen blockiert, bis die andere Seite bereit ist. Diese Methode kann zur Synchronisation zwischen Goroutinen verwendet werden, ohne dass explizite Sperren oder Bedingungsvariablen erforderlich sind.
Wie im offiziellen Beispiel,x, y := <-c, <-cDiese Zeile blockiert, bis das Berechnungsergebnis an den Channel gesendet wird.
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}Gepufferte Kanäle
Der zweite Parameter von make gibt die Puffergröße an:ch := make(chan int, 100)。
Durch die Verwendung von Pufferung kann Blockieren so weit wie möglich vermieden und die Anwendungsleistung verbessert werden.
Range
for …… rangeDie range-Anweisung kann Channels verarbeiten.
func main() {
go func() {
time.Sleep(1 * time.Hour)
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("Finished")
}
range cDie erzeugten Iterationswerte sind die im Channel gesendeten Werte. Sie wird so lange weiter iterieren, bis der Channel geschlossen ist. Im obigen Beispiel, wenn Sieclose(c)es auskommentieren, wird das Programm für immer blockieren beifor …… rangedieser Zeile.
select
selectDie select-Anweisung wählt aus einer Menge möglicher Sende- und Empfangsoperationen aus, die behandelt werden sollen. Sie ist ähnlich wieswitch, wird aber nur verwendet, um Kommunikationsoperationen zu behandeln.
Ihrecasekann eine Sendeanweisung, eine Empfangsanweisung oder einedefault。
receiveAnweisung kann einer oder zwei Variablen einen Wert zuweisen. Sie muss eine Empfangsoperation sein.
Höchstens einedefault case, es kann an beliebiger Stelle in der case-Liste stehen, obwohl wir es meistens ans Ende setzen.
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
Wenn mehrere Cases gleichzeitig zu verarbeiten sind, zum Beispiel können mehrere Kanäle Daten empfangen, dann wählt Go pseudozufällig einen Case zur Verarbeitung aus. Wenn kein Case verarbeitet werden muss, wählt esdefaultzur Verarbeitung aus, wenndefault caseexistiert. Wenn es keinendefault case, dannselectwird die Anweisung blockieren, bis ein Case verarbeitet werden muss.
Es sollte beachtet werden, dass Operationen auf nil-Kanälen immer blockiert werden. Wenn es keinen default-Case gibt, wird ein select mit nur nil-Kanälen für immer blockiert.
selectDie select-Anweisung und die switchswitch-Anweisung sind ähnlich; sie ist keine Schleife, sie wählt nur einen Case zur Bearbeitung aus. Wenn Sie weiterhin Kanäle verarbeiten möchten, können Sie eine endlose for-Schleife darum herum legen:
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
timeout
selectEine sehr wichtige Anwendung ist die Timeout-Behandlung. Denn wie oben erwähnt, wenn kein Case verarbeitet werden muss, blockiert die select-Anweisung für immer. Zu diesem Zeitpunkt benötigen wir möglicherweise eine Timeout-Operation, um Timeout-Situationen zu behandeln.
Im folgenden Beispiel senden wir nach 2 Sekunden einen Datenwert an Kanal c1, aberselectauf ein 1-Sekunden-Timeout gesetzt, also werden wir ausgebentimeout 1, stattresult 1。
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
Tatsächlich verwendet es dietime.AfterMethode, die einen Kanal vom Typ<-chan Timeeines unidirektionalen Kanals, der die aktuelle Zeit zum angegebenen Zeitpunkt an den zurückgegebenen Kanal sendet.
Timer und Ticker
Schauen wir uns zwei zeitbezogene Kanäle an.
Ein Timer ist ein Timer, der ein einzelnes zukünftiges Ereignis darstellt. Sie können dem Timer sagen, wie lange er warten soll; er stellt einen Kanal bereit, und zu diesem zukünftigen Zeitpunkt liefert der Kanal einen Zeitwert. Im folgenden Beispiel blockiert die zweite Zeile etwa 2 Sekunden lang und wird erst fortgesetzt, wenn der Zeitpunkt gekommen ist.
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
Natürlich, wenn Sie einfach nur warten möchten, können Sietime.Sleepum dies zu erreichen.
Du kannst auchtimer.Stopum den Timer zu stoppen.
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
tickerEin Ticker ist ein Timer, der periodisch auslöst. Er sendet in einem Intervall ein Ereignis (die aktuelle Zeit) an den Kanal, und der Empfänger des Kanals kann in festen Intervallen Ereignisse aus dem Kanal lesen. Im folgenden Beispiel löst der Ticker alle 500 Millisekunden aus; Sie können die Ausgabezeiten beobachten.
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
Ähnlich wie ein Timer kann auch ein Ticker dieStopMethode zum Stoppen verwenden. Sobald er gestoppt wurde, erhalten Empfänger keine Daten mehr vom Kanal.
close
Die integrierte close-Methode kann verwendet werden, um einen Kanal zu schließen.
Fassen wir die Operationen von Sendern und Empfängern zusammen, nachdem ein Channel geschlossen wurde.
Wenn der Channel c geschlossen wurde, wird das fortgesetzte Senden von Daten an ihn verursachenpanic: send on closed channel:
import "time"
func main() {
go func() {
time.Sleep(time.Hour)
}()
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
c <- 3
}
Aber aus diesem geschlossenen Channel kann man nicht nur die bereits gesendeten Daten lesen, sondern auch kontinuierlich Nullwerte lesen:
c := make(chan int, 10) c <- 1 c <- 2 close(c) fmt.Println(<-c) //1 fmt.Println(<-c) //2 fmt.Println(<-c) //0 fmt.Println(<-c) //0
Aber wenn manrangezum Lesen verwendet, wird die for-Schleife nach dem Schließen des Channels verlassen:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
fmt.Println(i)
}
Indem Siei, ok := <-ckann man den Status des Channels überprüfen, um festzustellen, ob der Wert ein Nullwert oder ein normal gelesener Wert ist.
c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false
Synchronisation
Channels können zur Synchronisierung zwischen Goroutines verwendet werden.
Im folgenden Beispiel wartet die Haupt-Goroutine über den done-Channel darauf, dass der Worker seine Aufgabe abschließt. Nachdem der Worker die Aufgabe beendet hat, muss er nur ein Datenelement an den Channel senden, um die Haupt-Goroutine zu benachrichtigen, dass die Aufgabe abgeschlossen ist.
import (
"fmt"
"time"
)
func worker(done chan bool) {
time.Sleep(time.Second)
// 通知任务已完成
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
// 等待任务完成
<-done
}
Quelle: http://colobu.com/2016/04/14/Golang-Channels/