Heim > Artikel > Backend-Entwicklung > Eine vorläufige Untersuchung von Goroutine und Channel in der Go-Sprache
Dieser Artikel wird Ihnen ein erstes Verständnis von Goroutine und Channel in der Go-Sprache vermitteln. Ich hoffe, er wird Ihnen hilfreich sein!
Die Implementierung des CSP
-Parallelitätsmodells der Go-Sprache enthält zwei Hauptkomponenten: eine ist Goroutine
und die andere ist channel
. In diesem Artikel werden ihre grundlegende Verwendung und Vorsichtsmaßnahmen vorgestellt. 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
ist die grundlegende Ausführungseinheit der Go
-Anwendung Es handelt sich um einen leichten Thread auf Benutzerebene, dessen zugrunde liegende Ebene durch coroutine
(coroutine) implementiert wird. Wie wir alle wissen, ist eine Coroutine ein Benutzerthread, der im Benutzermodus ausgeführt wird. Daher wird Goroutine
auch geplant, wenn das Programm Go
ausgeführt wird. 🎜🎜Syntax: go + Funktion/Methode 🎜🎜By go Schlüsselwort + Funktion/Methode kann eine
Goroutine
erstellen. 🎜🎜Codebeispiel: 🎜func send(ch chan<- int) { ch <- 1 } func recv(ch <-chan int) { <-ch }🎜Ausführungsergebnis: 🎜
func main() { ch := make(chan int, 5) ch <- 1 close(ch) ch <- 2 // panic: send on closed channel }🎜Wenn mehrere
Goroutine
vorhanden sind, ist ihre Ausführungsreihenfolge nicht festgelegt. Daher sind die Ergebnisse bei jedem Ausdruck unterschiedlich. 🎜🎜Wie aus dem Code ersichtlich ist, können wir mit dem Schlüsselwort go
goroutine
basierend auf benannter Funktion/Methode, auch goroutine
kann basierend auf anonymer Funktion / closure erstellt werden. 🎜🎜Wie wird Goroutine
beendet? Unter normalen Umständen bedeutet dies, dass Goroutine
beendet wird, solange die Ausführung der Funktion Goroutine
endet oder die Ausführung zurückkehrt. Wenn die Funktion oder Methode von Goroutine
einen Rückgabewert hat, wird dieser ignoriert, wenn Goroutine
beendet wird. 🎜channel
spielt eine wichtige Rolle im Go-Parallelitätsmodell. Es kann zum Implementieren der Kommunikation zwischen Goroutine
und auch zum Implementieren der Synchronisierung zwischen Goroutine
verwendet werden. 🎜channel
ist ein zusammengesetzter Datentyp und channel
. 🎜🎜Deklarationssyntax: var ch chan string🎜🎜Deklarieren Sie einen
Kanal
, dessen Elementtyp string
ist, über den obigen Code, der nur string. channel
ist ein Referenztyp und muss initialisiert werden, bevor Daten geschrieben werden können. Die Initialisierung erfolgt über 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 }🎜Sie können Daten an die
channel
-Variable ch
über ch <- xxx
und über x := < senden. - ch kann Daten von der <code>channel
-Variable ch
empfangen. 🎜channel
nicht angegeben wird, dann Was ist Es wird ein ungepufferter Kanal
erstellt: 🎜rrreee🎜Die Sende- und Empfangsvorgänge des ungepufferten Kanals
sind synchron. Nachdem der Sendevorgang ausgeführt wurde, wird die entsprechende Goroutine erstellt. code> blockiert, bis eine andere <code>Goroutine
verfügbar ist, um den Empfangsvorgang auszuführen, und umgekehrt. Was passiert, wenn der Sendevorgang und der Ausführungsvorgang unter derselben Goroutine platziert werden? Schauen Sie sich den folgenden Code an: 🎜rrreee🎜Nachdem das Programm ausgeführt wurde, wird bei ch <-
ein schwerwiegender Fehler
angezeigt, der dazu führt, dass alle Goroutine befinden sich im Ruhezustand, d. h. im Deadlock-Zustand. Um diese Situation zu vermeiden, müssen wir die Sende- und Empfangsvorgänge von <code>channel
in verschiedenen Goroutine
ausführen. 🎜rrreee🎜Aus dem obigen Beispiel kann geschlossen werden: Die Sende- und Empfangsvorgänge des ungepufferten Kanals
müssen in zwei verschiedenen Goroutinen
ausgeführt werden, andernfalls Deadlock Code> Bild. 🎜channel
erstellt: 🎜rrreee🎜Gepufferter channel
und ungepufferter chennel
sind unterschiedlich. Bei der Ausführung eines Sendevorgangs bleibt Goroutine
erst dann hängen, wenn der Puffer von channel
voll ist. Nur wenn >channel ausgeführt wird Ein Sendevorgang führt dazu, dass Goroutine
hängen bleibt. Codebeispiel: 🎜rrreeechannel
🎜rrreee🎜 erhält die Variable channel
über den obigen Code, und wir können darauf Sende- und Empfangsvorgänge ausführen. 🎜channel
🎜rrreee🎜Die Variable channel
wird über den obigen Code abgerufen und wir können nur Empfangsvorgänge für sie ausführen. 🎜channel
🎜rrreee🎜erhält die Variable channel
über den obigen Code, und wir können ihn nur senden. 🎜通常只发送 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
,以及关闭之后的一些注意事项。
Das obige ist der detaillierte Inhalt vonEine vorläufige Untersuchung von Goroutine und Channel in der Go-Sprache. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!