Heim  >  Artikel  >  Backend-Entwicklung  >  Eine vorläufige Untersuchung von Goroutine und Channel in der Go-Sprache

Eine vorläufige Untersuchung von Goroutine und Channel in der Go-Sprache

青灯夜游
青灯夜游nach vorne
2023-02-02 20:18:013233Durchsuche

Dieser Artikel wird Ihnen ein erstes Verständnis von Goroutine und Channel in der Go-Sprache vermitteln. Ich hoffe, er wird Ihnen hilfreich sein!

Eine vorläufige Untersuchung von Goroutine und Channel in der Go-Sprache

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

GoroutineGo 应用的基本执行单元,它是一种轻量的用户级线程,其底层是通过 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

channel 在 Go 并发模型中扮演者重要的角色。它可以用于实现 Goroutine 间的通信,也可以用来实现 Goroutine 间的同步。

channel 的基本操作

channel 是一种复合数据类型,声明时需要指定 channel 里元素的类型。

声明语法:var ch chan string

通过上述代码声明一个元素类型为 stringchannel,其只能存放 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

如果初始化 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 的只发送类型和只接收类型

  • 既能发送又能接收的 channel

    ch := make(chan int, 1)

    通过上述代码获得 channel 变量,我们可以对它执行发送与接收的操作。

  • 只接收的 channel

    ch := make(<-chan int, 1)

    通过上述代码获得 channel 变量,我们只能对它进行接收操作。

  • 只发送的 channel

    ch := make(chan<- int, 1)

    通过上述代码获得 channel

    Goroutine

    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. 🎜

    Grundlegende Verwendung

    🎜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

    🎜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. 🎜

    Grundfunktionen des Kanals

    🎜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. 🎜

    Gepufferter Kanal und ungepufferter Kanal

    🎜Wenn die Kapazität bei der Initialisierung von 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. 🎜
    🎜Wenn die Kapazität angegeben wird, wird ein gepufferter 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: 🎜rrreee

    Deklarieren Sie den Nur-Sende-Typ und den Nur-Empfangs-Typ des Kanals

    • 🎜Can sowohl senden als auch empfangen Der empfangene channel🎜rrreee🎜 erhält die Variable channel über den obigen Code, und wir können darauf Sende- und Empfangsvorgänge ausführen. 🎜
    • 🎜Nur Empfang von channel🎜rrreee🎜Die Variable channel wird über den obigen Code abgerufen und wir können nur Empfangsvorgänge für sie ausführen. 🎜
    • 🎜Nur der sendende 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
    }

    channel 的关闭

    通过内置函  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,以及关闭之后的一些注意事项。

    【相关推荐:Go视频教程编程教学

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!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen