Maison > développement back-end > Golang > Go Channel déverrouillé : comment ils fonctionnent

Go Channel déverrouillé : comment ils fonctionnent

Mary-Kate Olsen
Libérer: 2025-01-17 02:11:10
original
357 Les gens l'ont consulté

Canal Golang approfondi : principes de mise en œuvre et suggestions d'optimisation des performances

Le canal de Golang est un élément clé de son modèle de concurrence CSP et un pont de communication entre les Goroutines. Channel est fréquemment utilisé dans Golang et il est crucial d'avoir une compréhension approfondie de ses principes de mise en œuvre internes. Cet article analysera l'implémentation sous-jacente de Channel basée sur le code source Go 1.13.

Utilisation de base de la chaîne

Avant d'analyser formellement la mise en œuvre de Channel, passons en revue son utilisation de base :

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ce code montre deux opérations de base de Channel :

  • Opération d'envoi : c <- 1
  • Opération de réception : x := <-c

Le canal est divisé en canal tamponné et canal non tamponné. Le code ci-dessus utilise un canal non tamponné. Dans un canal non tamponné, si aucun autre Goroutine ne reçoit actuellement de données, l'expéditeur bloquera l'instruction d'envoi.

Vous pouvez spécifier la taille du tampon lors de l'initialisation du canal. Par exemple, make(chan int, 2) spécifie que la taille du tampon est de 2. Avant que le tampon ne soit plein, l'expéditeur peut envoyer des données sans blocage sans attendre que le destinataire soit prêt. Mais si le tampon est plein, l'expéditeur bloquera toujours.

Fonction d'implémentation sous-jacente du canal

Avant de plonger dans le code source de Channel, vous devez trouver l'emplacement d'implémentation spécifique de Channel dans Golang. Lors de l'utilisation de Channel, les fonctions sous-jacentes telles que runtime.makechan, runtime.chansend et runtime.chanrecv sont en fait appelées.

Vous pouvez utiliser la commande go tool compile -N -l -S hello.go pour convertir le code en instructions d'assemblage, ou utiliser l'outil en ligne Compiler Explorer (par exemple : go.godbolt.org/z/3xw5Cj). En analysant la notice de montage, on peut retrouver :

  • make(chan int) correspond à la fonction runtime.makechan.
  • c <- 1 correspond à la fonction runtime.chansend.
  • x := <-c correspond à la fonction runtime.chanrecv.

L'implémentation de ces fonctions se trouve dans le fichier runtime/chan.go du code source de Go.

Structure des chaînes

make(chan int) sera converti en fonction runtime.makechan par le compilateur, et sa signature de fonction est la suivante :

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Copier après la connexion
Copier après la connexion
Copier après la connexion

Parmi eux, t *chantype est le type d'élément Channel, size int est la taille du tampon spécifiée par l'utilisateur (0 si non spécifiée) et la valeur de retour est *hchan. hchan est la structure de mise en œuvre interne de Channel in Golang, définie comme suit :

<code class="language-go">type hchan struct {
        qcount   uint           // 缓冲区中已放入元素的数量
        dataqsiz uint           // 用户构造Channel时指定的缓冲区大小
        buf      unsafe.Pointer // 缓冲区
        elemsize uint16         // 缓冲区中每个元素的大小
        closed   uint32         // Channel是否关闭,==0表示未关闭
        elemtype *_type         // Channel元素的类型信息
        sendx    uint           // 缓冲区中发送元素的索引位置(发送索引)
        recvx    uint           // 缓冲区中接收元素的索引位置(接收索引)
        recvq    waitq          // 等待接收的Goroutine列表
        sendq    waitq          // 等待发送的Goroutine列表

        lock mutex
}</code>
Copier après la connexion
Copier après la connexion

Les attributs de hchan sont grossièrement divisés en trois catégories :

  • Attributs liés au tampon : tels que buf, dataqsiz, qcount, etc. Lorsque la taille du tampon du canal n'est pas 0, le tampon est utilisé pour stocker les données à recevoir et est implémenté à l'aide d'un tampon en anneau.
  • Attributs liés à la file d'attente : recvq contient Goroutine en attente de réception de données, sendq contient Goroutine en attente d'envoi de données. waitqMise en œuvre à l'aide d'une liste doublement chaînée.
  • Autres attributs : tels que lock, elemtype, closed, etc.
La fonction

makechan effectue principalement des contrôles de légalité et l'allocation de mémoire d'attributs tels que les tampons et hchan, qui ne seront pas abordés en profondeur ici.

Sur la base d'une simple analyse de l'attribut hchan, on peut voir qu'il existe deux composants importants : le tampon et la file d'attente. Tous les comportements et implémentations de hchan tournent autour de ces deux composants.

Envoi de données de chaîne

Les processus d'envoi et de réception de Channel sont très similaires. Analysez d'abord le processus d'envoi de Channel (par exemple c <- 1).

Lorsque

essaie d'envoyer des données au canal, si la file d'attente recvq n'est pas vide, une Goroutine en attente de réception de données sera retirée de l'en-tête recvq et les données seront envoyées directement à la Goroutine. Le code est le suivant :

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Copier après la connexion
Copier après la connexion
Copier après la connexion

recvq Contient Goroutine en attente de réception de données. Lorsqu'une Goroutine utilise une opération de réception (telle que x := <-c), si sendq n'est pas vide à ce moment, une Goroutine sera extraite de sendq et les données lui seront envoyées.

Si recvq est vide, cela signifie qu'il n'y a pas de Goroutine en attente de recevoir des données à ce moment-là, et la chaîne essaiera de mettre les données dans le tampon :

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Copier après la connexion
Copier après la connexion
Copier après la connexion

La fonction de ce code est très simple, c'est de mettre des données dans le tampon. Ce processus implique le fonctionnement d'un tampon en anneau, dataqsiz représente la taille du tampon spécifiée par l'utilisateur (la valeur par défaut est 0 si elle n'est pas spécifiée).

Si un canal non tamponné est utilisé ou si le tampon est plein (c.qcount == c.dataqsiz), les données à envoyer et la Goroutine actuelle seront regroupées dans un objet sudog, placé dans sendq, et le courant Goroutine sera configuré pour attendre Statut :

<code class="language-go">type hchan struct {
        qcount   uint           // 缓冲区中已放入元素的数量
        dataqsiz uint           // 用户构造Channel时指定的缓冲区大小
        buf      unsafe.Pointer // 缓冲区
        elemsize uint16         // 缓冲区中每个元素的大小
        closed   uint32         // Channel是否关闭,==0表示未关闭
        elemtype *_type         // Channel元素的类型信息
        sendx    uint           // 缓冲区中发送元素的索引位置(发送索引)
        recvx    uint           // 缓冲区中接收元素的索引位置(接收索引)
        recvq    waitq          // 等待接收的Goroutine列表
        sendq    waitq          // 等待发送的Goroutine列表

        lock mutex
}</code>
Copier après la connexion
Copier après la connexion

goparkunlock déverrouillera le mutex d'entrée et suspendra le Goroutine actuel, le mettant dans un état d'attente. gopark et goready apparaissent par paires et sont des opérations réciproques.

Du point de vue de l'utilisateur, après avoir appelé gopark, l'instruction de code pour l'envoi de données sera bloquée.

Réception des données de la chaîne

Le processus de réception de Channel est fondamentalement similaire au processus d'envoi, je n'entrerai donc pas dans les détails ici. Les opérations liées au tampon impliquées dans le processus de réception seront décrites en détail ultérieurement.

Il convient de noter que l'ensemble du processus d'envoi et de réception de Channel est verrouillé à l'aide de runtime.mutex. runtime.mutex est un verrou léger couramment utilisé dans le code source lié à l'exécution. L'ensemble du processus n'est pas la solution sans verrou la plus efficace. Il y a un problème concernant la chaîne sans verrouillage dans Golang : go/issues#8899.

Implémentation du tampon en anneau de canal

Channel utilise un tampon en anneau pour mettre en cache les données écrites. Les tampons en anneau présentent de nombreux avantages et sont idéaux pour implémenter des files d'attente FIFO de longueur fixe.

L'implémentation du ring buffer dans Channel est la suivante :

Il y a deux variables liées au tampon dans

hchan : recvx et sendx. sendx représente un index inscriptible dans le tampon, et recvx représente un index lisible dans le tampon. Les éléments entre recvx et sendx représentent les données qui ont été normalement placées dans le tampon.

Go Channel Unlocked: How They Work

Vous pouvez directement utiliser buf[recvx] pour lire le premier élément de la file d'attente, et utiliser buf[sendx] = x pour mettre l'élément en fin de file d'attente.

Écriture tampon

Lorsque le tampon n'est pas plein, l'opération de mise des données dans le tampon est la suivante :

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Copier après la connexion
Copier après la connexion
Copier après la connexion

chanbuf(c, c.sendx) équivaut à c.buf[c.sendx]. Le processus ci-dessus est très simple, copiez simplement les données vers l'emplacement tampon sendx.

Ensuite, passez sendx à la position suivante. Si sendx atteint la dernière position, il est mis à 0, ce qui est une approche typique de bout en bout.

Lecture du tampon

Lorsque le tampon n'est pas plein, sendq doit également être vide (car si le tampon n'est pas plein, la Goroutine envoyant les données ne sera pas mise en file d'attente, mais mettra directement les données dans le tampon). À l'heure actuelle, la logique de lecture du canal chanrecv est relativement simple. Les données peuvent être lues directement à partir du tampon. C'est également un processus de déplacement de recvx, qui est fondamentalement le même que l'écriture du tampon ci-dessus.

Quand il y a une Goroutine en attente dans sendq, le tampon doit être plein à ce moment. A l'heure actuelle, la logique de lecture de Channel est la suivante :

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Copier après la connexion
Copier après la connexion
Copier après la connexion

ep est l'adresse correspondant à la variable qui reçoit les données (par exemple, dans x := <-c, ep est l'adresse de x). sg représente le premier sendq extrait de sudog. Dans le code :

  • typedmemmove(c.elemtype, ep, qp) signifie copier l'élément actuellement lisible dans le tampon à l'adresse de la variable réceptrice.
  • typedmemmove(c.elemtype, qp, sg.elem) signifie copier les données en attente d'envoi par Goroutine dans sendq dans le tampon. Parce que recv est exécuté plus tard, cela équivaut à placer les données dans sendq à la fin de la file d'attente.

En termes simples, ici Channel copie les premières données du tampon dans la variable de réception correspondante, et copie en même temps les éléments de sendq à la fin de la file d'attente, implémentant ainsi FIFO (premier entré, premier sorti) .

Résumé

Channel est l'une des fonctionnalités les plus couramment utilisées dans Golang. Comprendre son code source vous aidera à mieux utiliser et comprendre Channel. Dans le même temps, ne soyez pas trop superstitieux et comptez sur les performances de Channel. La conception actuelle de Channel a encore beaucoup de place pour l'optimisation.

Suggestions d'optimisation :

  • Utilisez un mécanisme de verrouillage plus léger ou un système sans verrouillage pour améliorer les performances.
  • Optimisez la gestion des tampons et réduisez l'allocation de mémoire et les opérations de copie.

Leapcell : La meilleure plateforme sans serveur pour les applications Web Golang

Go Channel Unlocked: How They Work

Enfin, je recommande une plateforme très adaptée au déploiement des services Go : Leapcell

  1. Prise en charge multilingue : Prend en charge le développement JavaScript, Python, Go ou Rust.
  2. Déployez gratuitement un nombre illimité de projets : ne payez que ce que vous utilisez, sans demande, sans frais.
  3. Extrêmement rentable : payez au fur et à mesure, pas de frais d'inactivité. Par exemple : 25 $ prend en charge 6,94 millions de requêtes avec un temps de réponse moyen de 60 millisecondes.
  4. Expérience de développement fluide : interface utilisateur intuitive pour une configuration facile ; pipeline CI/CD entièrement automatisé et intégration de GitOps ; métriques et journaux en temps réel pour des informations exploitables.
  5. Évolutivité facile et hautes performances : mise à l'échelle automatique pour gérer facilement une simultanéité élevée, zéro surcharge opérationnelle, concentration sur la construction.
Go Channel Unlocked: How They Work

Veuillez consulter la documentation pour plus d'informations !

Twitter de Leapcell : https://www.php.cn/link/7884effb9452a6d7a7a79499ef854afd

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!

source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal