Le canal est un type de base en Go. Vous pouvez le considérer comme un tuyau à travers lequel des unités de base concurrentes peuvent envoyer ou recevoir des données pour communiquer.
Son opérateur est une flèche<- 。
ch <- v // 发送值v到Channel ch中 v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
(La direction de la flèche indique le flux de données)
Tout comme les types de données map et slice, un canal doit être créé avant de pouvoir être utilisé :
ch := make(chan int)
Type de canal
Le format de définition d'un type de canal est le suivant :
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
Il comprend trois types de définitions. La partie optionnelle<-représente la direction du canal. Si aucune direction n'est spécifiée, le canal est bidirectionnel, capable à la fois de recevoir et d'envoyer des données.
chan T // 可以接收和发送类型为 T 的数据 chan<- float64 // 只可以用来发送 float64 类型的数据 <-chan int // 只可以用来接收 int 类型的数据
<-Il se lie toujours d'abord au type le plus à gauche.
chan<- chan int // 等价 chan<- (chan int) chan<- <-chan int // 等价 chan<- (<-chan int) <-chan <-chan int // 等价 <-chan (<-chan int) chan (<-chan int)
UtilisationmakeInitialisez un canal, et vous pouvez définir sa capacité :
make(chan int, 100)
La capacité représente le nombre maximal d'éléments que le canal peut contenir, indiquant la taille du tampon du canal.
Si aucune capacité n'est définie, ou si la capacité est définie à 0, cela signifie que le canal n'a pas de tampon, et la communication ne se produira (de manière bloquante) que lorsque l'émetteur et le récepteur sont prêts. Si un tampon est défini, le blocage peut ne pas se produire : l'envoi ne bloque qu'une fois le tampon plein, et la réception ne bloque qu'une fois le tampon vide. Un canal nil ne communique jamais.
La fonction intégréecloseLa méthode peut fermer le canal.
Vous pouvez avoir plusieurs goroutines qui reçoivent/envoient des données depuis/vers un canal sans avoir besoin de mesures de synchronisation supplémentaires.
Un canal peut agir comme une file d'attente premier entré, premier sorti (FIFO), où l'ordre des données reçues correspond à l'ordre des données envoyées.
La réception du canal prend en chargel'affectation à valeurs multiples, comme
v, ok := <-ch
Il peut être utilisé pour vérifier si le canal a été fermé.
- instruction d'envoi
L'instruction d'envoi est utilisée pour envoyer des données vers un canal, par ex.ch <- 3。
Sa définition est la suivante :
SendStmt = Channel "<-" Expression . Channel = Expression .
Avant que la communication ne commence, le canal et l'expression doivent d'abord être évalués. Par exemple, dans ce qui suit, (3+4) est d'abord évalué à 7, puis envoyé au canal.
c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)
Avant que l'envoi puisse se poursuivre, la communication est bloquée. Comme mentionné précédemment, pour un canal non tamponné, l'envoi n'est exécuté qu'après que le récepteur est prêt. S'il y a un tampon et qu'il n'est pas plein, l'envoi sera exécuté.
L'envoi de données vers un canal déjà fermé provoqueraune panique à l'exécution。
L'envoi de données vers un canal nil sera toujours bloqué.
- opérateur de réception
<-chIl est utilisé pour recevoir des données du canal ch. Cette expression sera bloquée jusqu'à ce qu'il y ait des données à recevoir.
La réception de données depuis un canal nil sera toujours bloquée.
La réception de données depuis un canal fermé n'est pas bloquée ; elle renvoie immédiatement. Une fois toutes les données envoyées reçues, elle renvoie la valeur zéro du type d'élément.
Comme mentionné précédemment, vous pouvez utiliser un paramètre de retour supplémentaire pour vérifier si le canal est fermé.
x, ok := <-ch x, ok = <-ch var x, ok = <-ch
Si OK est false, cela indique que le x reçu est une valeur zéro générée, et que ce canal a été fermé ou est vide.
bloquant
Par défaut, l'envoi et la réception restent bloqués jusqu'à ce que l'autre côté soit prêt. Cette méthode peut être utilisée pour la synchronisation entre goroutines sans avoir besoin de verrous explicites ou de variables de condition.
Comme dans l'exemple officiel,x, y := <-c, <-cCette ligne sera bloquée jusqu'à ce que le résultat du calcul soit envoyé au canal.
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)
}Canaux tamponnés
Le deuxième paramètre de make spécifie la taille du tampon :ch := make(chan int, 100)。
Grâce à l'utilisation de la mise en mémoire tampon, le blocage peut être évité autant que possible, ce qui améliore les performances de l'application.
Range
for …… rangeL'instruction range peut traiter les canaux.
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 cLes valeurs d'itération générées sont les valeurs envoyées dans le canal. Il continuera d'itérer jusqu'à ce que le canal soit fermé. Dans l'exemple ci-dessus, si vousclose(c)le mettez en commentaire, le programme se bloquera indéfiniment surfor …… rangecette ligne.
select
selectL'instruction select choisit parmi un ensemble d'opérations d'envoi et de réception possibles à traiter. Elle est similaire àswitch, mais elle n'est utilisée que pour traiter des opérations de communication.
Sescasepeut être une instruction d'envoi, ou une instruction de réception, ou unedefault。
receiveinstruction peut affecter une valeur à une ou deux variables. Il doit s'agir d'une opération de réception.
Au plus undefault case, il peut être placé n'importe où dans la liste des cas, bien que nous le mettions le plus souvent à la fin.
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)
}
S'il y a plusieurs cas à traiter en même temps, par exemple, plusieurs canaux peuvent recevoir des données, alors Go sélectionnera de manière pseudo-aléatoire un cas à traiter. Si aucun cas n'a besoin d'être traité, il sélectionneradefaultà traiter, sidefault caseexiste. S'il n'y a pas dedefault case, puisselectl'instruction se bloquera jusqu'à ce qu'un cas ait besoin d'être traité.
Il convient de noter que les opérations sur les canaux nil sont toujours bloquées. S'il n'y a pas de cas default, un select avec uniquement des canaux nil sera bloqué pour toujours.
selectL'instruction select et le switchswitchsont similaires ; ce n'est pas une boucle, il sélectionne seulement un cas à traiter. Si vous voulez continuer à traiter des canaux, vous pouvez ajouter une boucle for infinie autour :
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
timeout
selectUne application très importante est la gestion des timeouts. Comme mentionné ci-dessus, si aucun cas n'a besoin d'être traité, l'instruction select se bloquera pour toujours. À ce moment-là, nous pouvons avoir besoin d'une opération de timeout pour gérer les situations de timeout.
Dans l'exemple suivant, nous envoyons une donnée au canal c1 après 2 secondes, maisselectdéfini sur un timeout de 1 seconde, donc nous afficheronstimeout 1, au lieu deresult 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")
}
}
En fait, il utilise latime.Afterméthode, qui renvoie un canal de type<-chan Timede canal unidirectionnel, qui envoie l'heure actuelle au canal renvoyé au moment spécifié.
Timer et Ticker
Examinons deux canaux liés au temps.
Un Timer est un minuteur qui représente un seul événement futur. Vous pouvez indiquer au minuteur combien de temps attendre ; il fournit un canal, et à ce moment futur le canal fournit une valeur temporelle. Dans l'exemple ci-dessous, la deuxième ligne se bloquera pendant environ 2 secondes et ne continuera pas tant que le moment n'est pas arrivé.
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
Bien sûr, si vous voulez simplement attendre, vous pouvez utilisertime.Sleeppour y parvenir.
Vous pouvez également utilisertimer.Stoppour arrêter le minuteur.
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
tickerUn Ticker est un minuteur qui se déclenche périodiquement. Il envoie un événement (l'heure actuelle) au canal à un intervalle donné, et le récepteur du canal peut lire les événements du canal à intervalles fixes. Dans l'exemple ci-dessous, le ticker se déclenche toutes les 500 millisecondes ; vous pouvez observer les instants de sortie.
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
Comme pour un minuteur, un Ticker peut aussi utiliser laStopméthode pour l'arrêter. Une fois qu'il s'arrête, les récepteurs ne recevront plus de données du canal.
close
La méthode close intégrée peut être utilisée pour fermer un canal.
Résumons les opérations des expéditeurs et des récepteurs après la fermeture d'un canal.
Si le canal c a été fermé, continuer à lui envoyer des données entraînerapanic: 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
}
Mais depuis ce canal fermé, vous pouvez non seulement lire les données déjà envoyées, mais aussi lire continuellement des valeurs zéro :
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
Mais si vous utilisezrangepour lire, la boucle for s'interrompra après la fermeture du canal :
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
fmt.Println(i)
}
En utilisanti, ok := <-cvous pouvez vérifier l'état du canal pour déterminer si la valeur est une valeur zéro ou une valeur lue normalement.
c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false
Synchronisation
Les canaux peuvent être utilisés pour la synchronisation entre goroutines.
Dans l'exemple suivant, la goroutine principale attend que le worker termine sa tâche via le canal done. Une fois la tâche terminée, il lui suffit d'envoyer une donnée dans le canal pour notifier à la goroutine principale que la tâche est terminée.
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
}
Source : http://colobu.com/2016/04/14/Golang-Channels/