Heim > Backend-Entwicklung > Golang > Go Channel Unlocked: So funktionieren sie

Go Channel Unlocked: So funktionieren sie

Mary-Kate Olsen
Freigeben: 2025-01-17 02:11:10
Original
357 Leute haben es durchsucht

Ausführlicher Golang-Kanal: Implementierungsprinzipien und Vorschläge zur Leistungsoptimierung

Golangs Kanal ist eine Schlüsselkomponente seines CSP-Parallelitätsmodells und eine Brücke für die Kommunikation zwischen Goroutinen. Channel wird in Golang häufig verwendet und es ist wichtig, ein tiefes Verständnis seiner internen Implementierungsprinzipien zu haben. In diesem Artikel wird die zugrunde liegende Implementierung von Channel basierend auf dem Go 1.13-Quellcode analysiert.

Grundlegende Nutzung des Kanals

Bevor wir die Implementierung von Channel offiziell analysieren, werfen wir einen Blick auf seine grundlegende Verwendung:

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

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

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

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Dieser Code zeigt zwei grundlegende Operationen von Channel:

  • Sendevorgang: c <- 1
  • Empfangsvorgang: x := <-c

Der Kanal ist in gepufferten Kanal und nicht gepufferten Kanal unterteilt. Der obige Code verwendet einen nicht gepufferten Kanal. Wenn in einem nicht gepufferten Kanal derzeit keine andere Goroutine Daten empfängt, blockiert der Absender die Sendeanweisung.

Sie können die Puffergröße beim Initialisieren des Kanals angeben. Beispielsweise gibt make(chan int, 2) die Puffergröße auf 2 an. Bevor der Puffer voll ist, kann der Sender Daten ohne Blockierung senden, ohne darauf warten zu müssen, dass der Empfänger bereit ist. Wenn der Puffer jedoch voll ist, blockiert der Absender trotzdem.

Grundliegende Implementierungsfunktion des Kanals

Bevor Sie in den Channel-Quellcode eintauchen, müssen Sie den spezifischen Implementierungsort von Channel in Golang finden. Bei Verwendung von Channel werden tatsächlich die zugrunde liegenden Funktionen wie runtime.makechan, runtime.chansend und runtime.chanrecv aufgerufen.

Sie können den Befehl go tool compile -N -l -S hello.go verwenden, um den Code in Montageanweisungen umzuwandeln, oder das Online-Tool Compiler Explorer verwenden (zum Beispiel: go.godbolt.org/z/3xw5Cj). Durch die Analyse der Montageanleitung können wir Folgendes finden:

  • make(chan int) entspricht der Funktion runtime.makechan.
  • c <- 1 entspricht der Funktion runtime.chansend.
  • x := <-c entspricht der Funktion runtime.chanrecv.

Die Implementierung dieser Funktionen befindet sich in der runtime/chan.go-Datei des Go-Quellcodes.

Kanalstruktur

make(chan int) wird vom Compiler in eine runtime.makechan-Funktion umgewandelt und ihre Funktionssignatur lautet wie folgt:

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Unter diesen ist t *chantype der Kanalelementtyp, size int die vom Benutzer angegebene Puffergröße (0, wenn nicht angegeben) und der Rückgabewert ist *hchan. hchan ist die interne Implementierungsstruktur von Channel in Golang, definiert wie folgt:

<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>
Nach dem Login kopieren
Nach dem Login kopieren

Die Attribute in hchan sind grob in drei Kategorien unterteilt:

  • Pufferbezogene Attribute: wie buf, dataqsiz, qcount usw. Wenn die Puffergröße des Kanals nicht 0 ist, wird der Puffer zum Speichern der zu empfangenden Daten verwendet und mithilfe eines Ringpuffers implementiert.
  • Wartewarteschlangenbezogene Attribute: recvq enthält Goroutine, die auf den Empfang von Daten wartet, sendq enthält Goroutine, die auf das Senden von Daten wartet. waitqImplementiert mithilfe einer doppelt verknüpften Liste.
  • Andere Attribute: wie lock, elemtype, closed usw.
Die Funktion

makechan führt hauptsächlich einige Legalitätsprüfungen und Speicherzuweisungen von Attributen wie Puffern und hchan durch, auf die hier nicht näher eingegangen wird.

Anhand einer einfachen Analyse des hchan-Attributs ist ersichtlich, dass es zwei wichtige Komponenten gibt: Puffer und Warteschlange. Alle Verhaltensweisen und Implementierungen von hchan drehen sich um diese beiden Komponenten.

Senden von Kanaldaten

Die Sende- und Empfangsprozesse von Channel sind sehr ähnlich. Analysieren Sie zunächst den Sendevorgang des Kanals (z. B. c <- 1).

Wenn

versucht, Daten an den Kanal zu senden und die recvq-Warteschlange nicht leer ist, wird eine Goroutine, die auf den Empfang von Daten wartet, aus dem recvq-Header entnommen und die Daten werden direkt an die Goroutine gesendet. Der Code lautet wie folgt:

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

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

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

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

recvq Enthält Goroutine, die auf den Empfang von Daten wartet. Wenn eine Goroutine eine Empfangsoperation verwendet (z. B. x := <-c) und sendq zu diesem Zeitpunkt nicht leer ist, wird eine Goroutine von sendq übernommen und die Daten werden an sie gesendet.

Wenn recvq leer ist, bedeutet dies, dass zu diesem Zeitpunkt keine Goroutine auf den Empfang von Daten wartet und der Kanal versucht, die Daten in den Puffer zu legen:

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Die Funktion dieses Codes ist sehr einfach, er besteht darin, Daten in den Puffer zu legen. Dieser Prozess beinhaltet den Betrieb eines Ringpuffers. dataqsiz stellt die vom Benutzer angegebene Puffergröße dar (standardmäßig 0, wenn nicht angegeben).

Wenn ein nicht gepufferter Kanal verwendet wird oder der Puffer voll ist (c.qcount == c.dataqsiz), werden die zu sendenden Daten und die aktuelle Goroutine in ein sudog-Objekt gepackt, in sendq platziert und im aktuellen Goroutine wird auf Wartestatus eingestellt:

<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>
Nach dem Login kopieren
Nach dem Login kopieren

goparkunlock entsperrt den Eingabe-Mutex, unterbricht die aktuelle Goroutine und versetzt sie in einen Wartezustand. gopark und goready erscheinen paarweise und sind reziproke Operationen.

Aus Sicht des Benutzers wird nach dem Aufruf von gopark die Codeanweisung zum Senden von Daten blockiert.

Kanaldatenempfang

Der Empfangsprozess von Channel ähnelt im Wesentlichen dem Sendeprozess, daher werde ich hier nicht auf Details eingehen. Die am Empfangsprozess beteiligten pufferbezogenen Vorgänge werden später ausführlich beschrieben.

Es ist zu beachten, dass der gesamte Sende- und Empfangsprozess des Kanals mit runtime.mutex gesperrt ist. runtime.mutex ist eine leichte Sperre, die häufig in laufzeitbezogenem Quellcode verwendet wird. Der gesamte Prozess ist nicht die effizienteste sperrenfreie Lösung. Es gibt ein Problem mit dem sperrfreien Kanal in Golang: go/issues#8899.

Channel-Ring-Puffer-Implementierung

Der Kanal verwendet einen Ringpuffer, um geschriebene Daten zwischenzuspeichern. Ringpuffer haben viele Vorteile und eignen sich ideal für die Implementierung von FIFO-Warteschlangen fester Länge.

Die Implementierung des Ringpuffers im Kanal ist wie folgt:

In

hchan gibt es zwei pufferbezogene Variablen: recvx und sendx. sendx stellt einen beschreibbaren Index im Puffer dar und recvx stellt einen lesbaren Index im Puffer dar. Elemente zwischen recvx und sendx stellen Daten dar, die normal in den Puffer gelegt wurden.

Go Channel Unlocked: How They Work

Sie können buf[recvx] direkt verwenden, um das erste Element der Warteschlange zu lesen, und buf[sendx] = x verwenden, um das Element am Ende der Warteschlange zu platzieren.

Pufferschreiben

Wenn der Puffer nicht voll ist, erfolgt das Einfügen von Daten in den Puffer wie folgt:

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

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

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

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

chanbuf(c, c.sendx) entspricht c.buf[c.sendx]. Der obige Vorgang ist sehr einfach. Kopieren Sie einfach die Daten in den Pufferspeicherort sendx.

Dann bewegen Sie sendx zur nächsten Position. Wenn sendx die letzte Position erreicht, wird sie auf 0 gesetzt, was einem typischen End-to-End-Ansatz entspricht.

Pufferablesung

Wenn der Puffer nicht voll ist, muss sendq auch leer sein (denn wenn der Puffer nicht voll ist, wird die Goroutine, die die Daten sendet, nicht in die Warteschlange gestellt, sondern legt die Daten direkt in den Puffer). Zu diesem Zeitpunkt ist die Leselogik von Kanal chanrecv relativ einfach. Daten können direkt aus dem Puffer gelesen werden. Es handelt sich auch um einen Prozess zum Verschieben von recvx, der im Grunde derselbe ist wie das oben beschriebene Pufferschreiben.

Wenn in sendq eine Goroutine wartet, muss der Puffer zu diesem Zeitpunkt voll sein. Zu diesem Zeitpunkt lautet die Leselogik von Channel wie folgt:

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

ep ist die Adresse, die der Variablen entspricht, die die Daten empfängt (in x := <-c ist ep beispielsweise die Adresse von x). sg stellt das erste sendq aus sudog dar. Im Code:

  • typedmemmove(c.elemtype, ep, qp) bedeutet das Kopieren des aktuell lesbaren Elements im Puffer an die Adresse der empfangenden Variablen.
  • typedmemmove(c.elemtype, qp, sg.elem) bedeutet, dass die Daten, die darauf warten, von Goroutine gesendet zu werden, in sendq in den Puffer kopiert werden. Da recv später ausgeführt wird, entspricht dies dem Platzieren der Daten in sendq am Ende der Warteschlange.

Einfach ausgedrückt kopiert der Kanal hier die ersten Daten im Puffer in die entsprechende Empfangsvariable und kopiert gleichzeitig die Elemente in sendq an das Ende der Warteschlange, wodurch FIFO (First In, First Out) implementiert wird. .

Zusammenfassung

Channel ist eine der am häufigsten genutzten Funktionen in Golang. Wenn Sie den Quellcode kennen, können Sie Channel besser nutzen und verstehen. Seien Sie gleichzeitig nicht zu abergläubisch und verlassen Sie sich auf die Leistung von Channel. Das aktuelle Design von Channel bietet noch viel Raum für Optimierung.

Optimierungsvorschläge:

  • Verwenden Sie einen leichteren Verriegelungsmechanismus oder ein sperrfreies Schema, um die Leistung zu verbessern.
  • Optimieren Sie die Pufferverwaltung und reduzieren Sie die Speicherzuweisung und Kopiervorgänge.

Leapcell: Die beste serverlose Plattform für Golang-Webanwendungen

Go Channel Unlocked: How They Work

Abschließend empfehle ich eine Plattform, die sich sehr gut für die Bereitstellung von Go-Diensten eignet: Leapcell

  1. Mehrsprachige Unterstützung: Unterstützt die Entwicklung von JavaScript, Python, Go oder Rust.
  2. Stellen Sie unbegrenzt viele Projekte kostenlos bereit: Zahlen Sie nur für das, was Sie nutzen, keine Anfragen, keine Gebühren.
  3. Extrem kostengünstig: Pay-as-you-go, keine Leerlaufgebühren. Beispiel: 25 $ unterstützt 6,94 Millionen Anfragen mit einer durchschnittlichen Antwortzeit von 60 Millisekunden.
  4. Reibungslose Entwicklererfahrung: Intuitive Benutzeroberfläche für einfache Einrichtung; vollautomatische CI/CD-Pipeline und GitOps-Integration für umsetzbare Erkenntnisse;
  5. Einfache Skalierbarkeit und hohe Leistung: Automatische Skalierung zur problemlosen Bewältigung hoher Parallelität; kein betrieblicher Aufwand, konzentrieren Sie sich auf die Erstellung.
Go Channel Unlocked: How They Work

Weitere Informationen finden Sie in der Dokumentation!

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

Das obige ist der detaillierte Inhalt vonGo Channel Unlocked: So funktionieren sie. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage