Maison > Article > développement back-end > Une exploration préliminaire de Goroutine et du canal en langage Go
Cet article vous donnera une compréhension préliminaire de Goroutine et du canal en langage Go. J'espère qu'il vous sera utile !
L'implémentation du modèle de concurrence CSP
du langage Go contient deux composants principaux : l'un est Goroutine
et l'autre est canal
. Cet article présentera leur utilisation de base et leurs précautions. CSP
并发模型的实现包含两个主要组成部分:一个是 Goroutine
,另一个是 channel
。本文将会介绍它们的基本用法和注意事项。
Goroutine
是 Go
应用的基本执行单元,它是一种轻量的用户级线程,其底层是通过 coroutine
(协程)去实现的并发。众所周知,协程是一种运行在用户态的用户线程,因此 Goroutine
也是被调度于 Go
程序运行时。
语法:go + 函数/方法
通过 go 关键字 + 函数/方法 可以创建一个 Goroutine
。
代码示例:
import ( "fmt" "time" ) func printGo() { fmt.Println("具名函数") } type G struct { } func (g G) g() { fmt.Println("方法") } func main() { // 基于具名函数创建 goroutine go printGo() // 基于方法创建 goroutine g := G{} go g.g() // 基于匿名函数创建 goroutine go func() { fmt.Println("匿名函数") }() // 基于闭包创建 goroutine i := 0 go func() { i++ fmt.Println("闭包") }() time.Sleep(time.Second) // 避免 main goroutine 结束后,其创建的 goroutine 来不及运行,因此在此休眠 1 秒 }
执行结果:
闭包 具名函数 方法 匿名函数
当多个 Goroutine
存在时,它们的执行顺序是不固定的。因此每次打印的结果都不相同。
由代码可知,通过 go
关键字,我们可以基于 具名函数 / 方法 创建 goroutine
,也可以基于 匿名函数 / 闭包 创建 goroutine
。
那么 Goroutine
是如何退出的呢?正常情况下,只要 Goroutine
函数执行结束,或者执行返回,意味着 Goroutine
的退出。如果 Goroutine
的函数或方法有返回值,在 Goroutine
退出时会将其忽略。
channel
在 Go 并发模型中扮演者重要的角色。它可以用于实现 Goroutine
间的通信,也可以用来实现 Goroutine
间的同步。
channel
是一种复合数据类型,声明时需要指定 channel
里元素的类型。
声明语法:var ch chan string
通过上述代码声明一个元素类型为 string
的 channel
,其只能存放 string
类型的元素。channel
是引用类型,必须初始化才能写入数据,通过 make
的方式初始化。
import ( "fmt" ) func main() { var ch chan string ch = make(chan string, 1) // 打印 chan 的地址 fmt.Println(ch) // 向 ch 发送 "Go" 数据 ch <- "Go" // 从 ch 中接收数据 s := <-ch fmt.Println(s) // Go }
通过 ch <- xxx
可以向 channel
变量 ch
发送数据,通过 x := <- ch
可以从 channel
变量 ch
中接收数据。
如果初始化 channel
时,不指定容量时,则创建的是一个无缓冲的 channel
:
ch := make(chan string)
无缓冲的 channel
的发送与接收操作是同步的,在执行发送操作之后,对应 Goroutine
将会阻塞,直到有另一个 Goroutine
去执行接收操作,反之亦然。如果将发送操作和执行操作放在同一个 Goroutine 下进行,会发生什么操作呢?看看下述代码:
import ( "fmt" ) func main() { ch := make(chan int) // 发送数据 ch <- 1 // fatal error: all goroutines are asleep - deadlock! // 接收数据 n := <-ch fmt.Println(n) }
程序运行之后,会在 ch <-
处得到 fatal error
,提示所有的 Goroutine
处于休眠状态,也就是死锁了。为避免这种情况,我们需要将 channel
的发送操作和接收操作放到不同的 Goroutine
中执行。
import ( "fmt" ) func main() { ch := make(chan int) go func() { // 发送数据 ch <- 1 }() // 接收数据 n := <-ch fmt.Println(n) // 1 }
由上述例子可以得出结论:无缓冲 channel
的发送与接收操作,一定要放在两个不同的 Goroutine
中进行,否则会发生 deadlock
形象。
如果指定容量,则创建的是一个带缓冲的 channel
:
ch := make(chan string, 5)
有缓冲的 channel
与无缓冲的 chennel
有所区别,执行发送操作时,只要 channel
的缓冲区未满,Goroutine
不会挂起,直到缓冲区满时,再向 channel
执行发送操作,才会导致 Goroutine
挂起。代码示例:
func main() { ch := make(chan int, 1) // 发送数据 ch <- 1 ch <- 2 // fatal error: all goroutines are asleep - deadlock! }
既能发送又能接收的 channel
ch := make(chan int, 1)
通过上述代码获得 channel
变量,我们可以对它执行发送与接收的操作。
只接收的 channel
ch := make(<-chan int, 1)
通过上述代码获得 channel
变量,我们只能对它进行接收操作。
只发送的 channel
ch := make(chan<- int, 1)
通过上述代码获得 channel
Goroutine
est l'unité d'exécution de base de l'application Go
. Il s'agit d'un thread léger au niveau utilisateur, et sa couche sous-jacente est une concurrence implémentée via coroutine
(coroutine). Comme nous le savons tous, une coroutine est un thread utilisateur exécuté en mode utilisateur, donc Goroutine
est également planifié lorsque le programme Go
est en cours d'exécution. 🎜🎜Syntaxe : go + fonction/méthode 🎜🎜Par mot-clé go + fonction/méthode peut créer une
Goroutine
. 🎜🎜Exemple de code : 🎜func send(ch chan<- int) { ch <- 1 } func recv(ch <-chan int) { <-ch }🎜Résultat de l'exécution : 🎜
func main() { ch := make(chan int, 5) ch <- 1 close(ch) ch <- 2 // panic: send on closed channel }🎜Lorsque plusieurs
Goroutine
existent, leur ordre d'exécution n'est pas fixe. Par conséquent, les résultats seront différents à chaque impression. 🎜🎜Comme le montre le code, grâce au mot-clé go
, nous pouvons créer une goroutine
basée sur une fonction nommée / méthode, une goroutine
peut également être créée sur la base d'une fonction anonyme / fermeture. 🎜🎜Alors, comment se termine Goroutine
? Dans des circonstances normales, tant que l'exécution de la fonction Goroutine
se termine ou que l'exécution revient, cela signifie la sortie de Goroutine
. Si la fonction ou la méthode de Goroutine
a une valeur de retour, elle sera ignorée à la sortie de Goroutine
. 🎜channel
joue un rôle important dans le modèle de concurrence Go. Il peut être utilisé pour implémenter la communication entre Goroutine
, et peut également être utilisé pour implémenter la synchronisation entre Goroutine
. 🎜channel
est un type de données composite et channel
. 🎜🎜Syntaxe de déclaration : var ch chan string🎜🎜Déclarez un
channel
dont le type d'élément est string
via le code ci-dessus, qui ne peut stocker que string. channel
est un type de référence et doit être initialisé pour écrire des données. Il est initialisé via make
. 🎜import "fmt" func main() { ch := make(chan int, 5) ch <- 1 close(ch) fmt.Println(<-ch) // 1 n, ok := <-ch fmt.Println(n) // 0 fmt.Println(ok) // false }🎜Vous pouvez envoyer des données à la variable
channel
ch
via ch <- xxx
et via x := < - ch peut recevoir des données de la variable <code>channel
ch
. 🎜canal
, alors qu'est-ce que créé est un canal
sans tampon : 🎜rrreee🎜Les opérations d'envoi et de réception du canal
sans tampon sont synchrones. Une fois l'opération d'envoi effectuée, le Goroutine
correspondant. code> bloquera jusqu'à ce qu'un autre Goroutine
soit disponible pour effectuer l'opération de réception, et vice versa. Que se passera-t-il si l'opération d'envoi et l'opération d'exécution sont placées sous le même Goroutine ? Jetez un œil au code suivant : 🎜rrreee🎜Une fois le programme exécuté, vous obtiendrez une erreur fatale
à ch <-
, vous invitant à ce que tous les Goroutine sont en état de veille, c'est-à-dire dans une impasse. Pour éviter cette situation, nous devons exécuter les opérations d'envoi et de réception du <code>channel
dans différents Goroutine
. 🎜rrreee🎜On peut conclure de l'exemple ci-dessus : les opérations d'envoi et de réception de channel
non tamponné doivent être effectuées dans deux Goroutine
différentes, sinon impasse code>image. 🎜<hr>🎜Si la capacité est spécifiée, un <code>canal
tamponné est créé : 🎜rrreee🎜Le canal
tamponné et le chenal
non tamponné sont différents. Lors de l'exécution d'une opération d'envoi, tant que le tampon du channel
n'est pas plein, Goroutine
ne se bloquera pas tant que le tampon n'est pas plein. Uniquement lorsque >channel s'exécute. une opération d'envoi entraînera le blocage de Goroutine
. Exemple de code : 🎜rrreeechannel
🎜rrreee🎜 reçu obtient la variable channel
via le code ci-dessus, et nous pouvons y effectuer des opérations d'envoi et de réception. 🎜channel
🎜rrreee🎜La variable channel
est obtenue via le code ci-dessus, et nous ne pouvons effectuer des opérations de réception que dessus. 🎜channel
d'envoi 🎜rrreee🎜obtient la variable channel
via le code ci-dessus, et nous ne pouvons que l'envoyer. 🎜通常只发送 channel
类型和只接收 channel
类型,会被用作函数的参数类型或返回值:
func send(ch chan<- int) { ch <- 1 } func recv(ch <-chan int) { <-ch }
通过内置函 close(c chan<- Type)
,可以对 channel
进行关闭。
在发送端关闭 channel
在 channel
关闭之后,将不能对 channel
执行发送操作,否则会发生 panic
,提示 channel
已关闭。
func main() { ch := make(chan int, 5) ch <- 1 close(ch) ch <- 2 // panic: send on closed channel }
管道 channel
之后,依旧可以对 channel
执行接收操作,如果存在缓冲区的情况下,将会读取缓冲区的数据,如果缓冲区为空,则获取到的值为 channel
对应类型的零值。
import "fmt" func main() { ch := make(chan int, 5) ch <- 1 close(ch) fmt.Println(<-ch) // 1 n, ok := <-ch fmt.Println(n) // 0 fmt.Println(ok) // false }
如果通过 for-range 遍历 channel
时,中途关闭 channel
则会导致 for-range
循环结束。
本文首先介绍了 Goroutine
的创建方式以及其退出的时机是什么。
其次介绍了如何创建 channel
类型变量的有缓冲与无缓冲的创建方式。需要注意的是,无缓冲的 channel
发送与接收操作,需要在两个不同的 Goroutine
中执行,否则会发送 error
。
接下来介绍如何定义只发送和只接收的 channel
类型。通常只发送 channel
类型和只接收 channel
类型,会被用作函数的参数类型或返回值。
最后介绍了如何关闭 channel
,以及关闭之后的一些注意事项。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!