Maison >développement back-end >Golang >Une exploration préliminaire de Goroutine et du canal en langage Go

Une exploration préliminaire de Goroutine et du canal en langage Go

青灯夜游
青灯夜游avant
2023-02-02 20:18:013272parcourir

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 !

Une exploration préliminaire de Goroutine et du canal en langage Go

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

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

    Utilisation de base

    🎜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

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

    Opérations de base du canal

    🎜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 tamponné et canal non tamponné

    🎜Si la capacité n'est pas spécifiée lors de l'initialisation du 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 : 🎜rrreee

    Déclarez le type d'envoi uniquement et le type de réception uniquement du canal

    • 🎜Peut à la fois envoyer et recevoir Le channel🎜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. 🎜
    • 🎜Réception uniquement du 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. 🎜
    • 🎜Seul le 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
    }

    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视频教程编程教学

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer