Heim > Backend-Entwicklung > Golang > Wie erhalte ich die Goroutine-ID?

Wie erhalte ich die Goroutine-ID?

Mary-Kate Olsen
Freigeben: 2025-01-04 10:45:35
Original
194 Leute haben es durchsucht

How to Get the Goroutine ID?

In einem Betriebssystem hat jeder Prozess eine eindeutige Prozess-ID und jeder Thread hat seine eigene eindeutige Thread-ID. In ähnlicher Weise verfügt in der Go-Sprache jede Goroutine über eine eigene eindeutige Go-Routinen-ID, die häufig in Szenarien wie Panik auftritt. Obwohl Goroutinen über inhärente IDs verfügen, bietet die Go-Sprache bewusst keine Schnittstelle zum Abrufen dieser ID. Dieses Mal werden wir versuchen, die Goroutine-ID über die Go-Assemblersprache zu erhalten.

1. Das offizielle Design, kein Goid zu haben (https://github.com/golang/go/issues/22770)

Den offiziellen relevanten Materialien zufolge stellt die Go-Sprache absichtlich kein Goid bereit, um Missbrauch zu vermeiden. Weil die meisten Benutzer, nachdem sie den Goid leicht erhalten haben, unbewusst Code schreiben, der in der nachfolgenden Programmierung stark vom Goid abhängt. Eine starke Abhängigkeit von Goid erschwert die Portierung dieses Codes und verkompliziert auch das gleichzeitige Modell. Gleichzeitig gibt es möglicherweise eine große Anzahl von Goroutinen in der Go-Sprache, es ist jedoch nicht einfach, in Echtzeit zu überwachen, wann jede Goroutine zerstört wird, was auch dazu führt, dass Ressourcen, die von Goid abhängen, nicht automatisch recycelt werden ( manuelles Recycling erforderlich). Wenn Sie jedoch ein Go-Assembler-Benutzer sind, können Sie diese Bedenken völlig ignorieren.

Hinweis: Wenn Sie den Goid gewaltsam erhalten, könnten Sie „beschämt“ sein?:
https://github.com/golang/go/blob/master/src/runtime/proc.go#L7120

2. Goid in Pure Go erhalten

Um das Verständnis zu erleichtern, versuchen wir zunächst, den Goid in reinem Go zu erhalten. Obwohl die Leistung beim Erhalten des Goids in reinem Go relativ gering ist, ist der Code gut portierbar und kann auch zum Testen und Überprüfen verwendet werden, ob der mit anderen Methoden erhaltene Goid korrekt ist.

Jeder Go-Sprachbenutzer sollte die Panikfunktion kennen. Der Aufruf der Panic-Funktion führt zu einer Goroutine-Ausnahme. Wenn die Panik nicht von der Wiederherstellungsfunktion behandelt wird, bevor die Stammfunktion der Goroutine erreicht wird, gibt die Laufzeit relevante Ausnahme- und Stapelinformationen aus und beendet die Goroutine.

Lassen Sie uns ein einfaches Beispiel konstruieren, um den Goid durch Panik auszugeben:

package main

func main() {
    panic("leapcell")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Nach dem Ausführen werden folgende Informationen ausgegeben:

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir können vermuten, dass die 1 in der Panic-Ausgabeinformation Goroutine 1 [läuft] das Goid ist. Aber wie können wir die Informationen zur Panikausgabe im Programm erhalten? Tatsächlich handelt es sich bei den obigen Informationen lediglich um eine Textbeschreibung des aktuellen Funktionsaufrufstapelrahmens. Die runtime.Stack-Funktion bietet die Funktion zum Abrufen dieser Informationen.

Lassen Sie uns ein Beispiel rekonstruieren, das auf der runtime.Stack-Funktion basiert, um die Goid auszugeben, indem die Informationen des aktuellen Stapelrahmens ausgegeben werden:

package main

func main() {
    panic("leapcell")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Nach dem Ausführen werden folgende Informationen ausgegeben:

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Daher ist es einfach, die Goid-Informationen aus der von runtime.Stack erhaltenen Zeichenfolge zu analysieren:

package main

import "runtime"

func main() {
    var buf = make([]byte, 64)
    var stk = buf[:runtime.Stack(buf, false)]
    print(string(stk))
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir werden nicht näher auf die Details der GetGoid-Funktion eingehen. Es ist zu beachten, dass die runtime.Stack-Funktion nicht nur die Stapelinformationen der aktuellen Goroutine, sondern auch die Stapelinformationen aller Goroutinen abrufen kann (gesteuert durch den zweiten Parameter). Gleichzeitig erhält die Funktion net/http2.curGoroutineID in der Go-Sprache den Goid auf ähnliche Weise.

3. Erhalten von Goid aus der g-Struktur

Laut der offiziellen Go-Assembler-Dokumentation wird der g-Zeiger jeder laufenden Goroutine-Struktur im lokalen Speicher TLS des Systemthreads gespeichert, in dem sich die aktuell laufende Goroutine befindet. Wir können zuerst den lokalen TLS-Thread-Speicher abrufen, dann den Zeiger der g-Struktur vom TLS abrufen und schließlich den Goid aus der g-Struktur extrahieren.

Im Folgenden erhalten Sie den g-Zeiger, indem Sie auf das im Laufzeitpaket definierte get_tls-Makro verweisen:

goroutine 1 [running]:
main.main()
    /path/to/main.g
Nach dem Login kopieren
Nach dem Login kopieren

get_tls ist eine Makrofunktion, die in der Header-Datei runtime/go_tls.h definiert ist.

Für die AMD64-Plattform ist die Makrofunktion get_tls wie folgt definiert:

import (
    "fmt"
    "strconv"
    "strings"
    "runtime"
)

func GetGoid() int64 {
    var (
        buf [64]byte
        n   = runtime.Stack(buf[:], false)
        stk = strings.TrimPrefix(string(buf[:n]), "goroutine")
    )

    idField := strings.Fields(stk)[0]
    id, err := strconv.Atoi(idField)
    if err!= nil {
        panic(fmt.Errorf("can not get goroutine id: %v", err))
    }

    return int64(id)
}
Nach dem Login kopieren
Nach dem Login kopieren

Nach dem Erweitern der Makrofunktion get_tls lautet der Code zum Abrufen des g-Zeigers wie folgt:

get_tls(CX)
MOVQ g(CX), AX     // Move g into AX.
Nach dem Login kopieren
Nach dem Login kopieren

Tatsächlich ähnelt TLS der Adresse des lokalen Thread-Speichers, und die Daten im Speicher, die der Adresse entsprechen, sind der G-Zeiger. Wir können direkter sein:

#ifdef GOARCH_amd64
#define        get_tls(r)        MOVQ TLS, r
#define        g(r)        0(r)(TLS*1)
#endif
Nach dem Login kopieren
Nach dem Login kopieren

Basierend auf der obigen Methode können wir eine getg-Funktion umschließen, um den g-Zeiger zu erhalten:

MOVQ TLS, CX
MOVQ 0(CX)(TLS*1), AX
Nach dem Login kopieren

Dann erhalten Sie im Go-Code den Wert von goid durch den Offset des goid-Elements in der g-Struktur:

MOVQ (TLS), AX
Nach dem Login kopieren

Hier ist g_goid_offset der Offset des Goid-Mitglieds. Die g-Struktur bezieht sich auf runtime/runtime2.go.

In der Go1.10-Version beträgt der Offset von goid 152 Bytes. Daher kann der obige Code nur in Go-Versionen korrekt ausgeführt werden, bei denen der Goid-Offset ebenfalls 152 Byte beträgt. Laut dem Orakel des großen Thompson sind Aufzählung und rohe Gewalt das Allheilmittel für alle schwierigen Probleme. Wir können die Goid-Offsets auch in einer Tabelle speichern und dann den Goid-Offset entsprechend der Go-Versionsnummer abfragen.

Das Folgende ist der verbesserte Code:

// func getg() unsafe.Pointer
TEXT ·getg(SB), NOSPLIT, <pre class="brush:php;toolbar:false">const g_goid_offset = 152 // Go1.10

func GetGroutineId() int64 {
    g := getg()
    p := (*int64)(unsafe.Pointer(uintptr(g) + g_goid_offset))
    return *p
}
Nach dem Login kopieren
-8 MOVQ (TLS), AX MOVQ AX, ret+0(FP) RET

Jetzt kann sich der Goid-Offset endlich automatisch an die veröffentlichten Go-Sprachversionen anpassen.

4. Erhalten des Schnittstellenobjekts, das der g-Struktur entspricht

Obwohl Aufzählung und Brute-Force unkompliziert sind, unterstützen sie die unveröffentlichten Go-Versionen, die sich in der Entwicklung befinden, nicht gut. Wir können den Offset des Goid-Mitglieds in einer bestimmten Version, die sich in der Entwicklung befindet, nicht im Voraus kennen.

Wenn es sich im Laufzeitpaket befindet, können wir den Offset des Mitglieds direkt über unsafe.OffsetOf(g.goid) ermitteln. Wir können den Typ der g-Struktur auch durch Reflektion ermitteln und dann den Offset eines bestimmten Elements über den Typ abfragen. Da es sich bei der g-Struktur um einen internen Typ handelt, kann der Go-Code die Typinformationen der g-Struktur nicht aus externen Paketen abrufen. In der Assemblersprache Go können wir jedoch alle Symbole sehen, sodass wir theoretisch auch die Typinformationen der g-Struktur erhalten können.

Nachdem ein Typ definiert wurde, generiert die Go-Sprache entsprechende Typinformationen für diesen Typ. Beispielsweise generiert die g-Struktur einen Typ-Laufzeit-g-Bezeichner, um die Werttypinformationen der g-Struktur darzustellen, und auch einen Typ-*Laufzeit-g-Bezeichner, um die Zeigertypinformationen darzustellen. Wenn die g-Struktur über Methoden verfügt, werden auch Typinformationen für go.itab.runtime.g und go.itab.*runtime.g generiert, um die Typinformationen mit Methoden darzustellen.

Wenn wir den Typ·Laufzeit·g erhalten können, der den Typ der g-Struktur und den g-Zeiger darstellt, können wir die Schnittstelle des g-Objekts erstellen. Das Folgende ist die verbesserte getg-Funktion, die die Schnittstelle des g-Zeigerobjekts zurückgibt:

package main

func main() {
    panic("leapcell")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hier entspricht das AX-Register dem g-Zeiger und das BX-Register dem Typ der g-Struktur. Anschließend wird die runtime·convT2E-Funktion verwendet, um den Typ in eine Schnittstelle zu konvertieren. Da wir nicht den Zeigertyp der g-Struktur verwenden, stellt die zurückgegebene Schnittstelle den Werttyp der g-Struktur dar. Theoretisch können wir auch eine Schnittstelle vom Typ g-Zeiger erstellen, aber aufgrund der Einschränkungen der Go-Assemblersprache können wir den Typ·*Runtime·g-Bezeichner nicht verwenden.

Basierend auf der von g zurückgegebenen Schnittstelle ist es einfach, die goid zu erhalten:

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der obige Code erhält den Goid direkt durch Reflexion. Theoretisch kann der Code normal ausgeführt werden, solange sich der Name der reflektierten Schnittstelle und des Goid-Mitglieds nicht ändert. Nach tatsächlichen Tests kann der obige Code in den Versionen Go1.8, Go1.9 und Go1.10 korrekt ausgeführt werden. Wenn sich der Name des g-Strukturtyps und der Reflexionsmechanismus der Go-Sprache nicht ändern, sollte er optimistischerweise auch in zukünftigen Go-Sprachversionen ausgeführt werden können.

Obwohl Reflexion ein gewisses Maß an Flexibilität aufweist, wurde die Leistung der Reflexion immer kritisiert. Eine verbesserte Idee besteht darin, den Offset des Goids durch Reflektion zu erhalten und dann den Goid durch den g-Zeiger und den Offset zu erhalten, sodass die Reflektion in der Initialisierungsphase nur einmal ausgeführt werden muss.

Das Folgende ist der Initialisierungscode für die g_goid_offset-Variable:

package main

import "runtime"

func main() {
    var buf = make([]byte, 64)
    var stk = buf[:runtime.Stack(buf, false)]
    print(string(stk))
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Nachdem Sie den richtigen Goid-Offset haben, erhalten Sie den Goid auf die zuvor beschriebene Weise:

package main

func main() {
    panic("leapcell")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Zu diesem Zeitpunkt ist unsere Implementierungsidee zum Erhalten des Goid vollständig genug, aber der Assembler-Code birgt immer noch ernsthafte Sicherheitsrisiken.

Obwohl die getg-Funktion als Funktionstyp deklariert ist, der die Stapelaufteilung mit dem NOSPLIT-Flag verbietet, ruft die getg-Funktion intern die komplexere runtime·convT2E-Funktion auf. Wenn die Funktion runtime·convT2E auf unzureichenden Stapelspeicher stößt, kann sie Stapelaufteilungsvorgänge auslösen. Wenn der Stapel geteilt wird, verschiebt der GC die Stapelzeiger in den Funktionsparametern, Rückgabewerten und lokalen Variablen. Unsere getg-Funktion stellt jedoch keine Zeigerinformationen für lokale Variablen bereit.

Das Folgende ist die vollständige Implementierung der verbesserten getg-Funktion:

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

NO_LOCAL_POINTERS bedeutet hier, dass die Funktion keine lokalen Zeigervariablen hat. Gleichzeitig wird die zurückgegebene Schnittstelle mit Nullwerten initialisiert und nach Abschluss der Initialisierung wird GO_RESULTS_INITIALIZED verwendet, um den GC zu informieren. Dadurch wird sichergestellt, dass der GC bei einer Stapelteilung die Zeiger in den Rückgabewerten und lokalen Variablen korrekt verarbeiten kann.

5. Anwendung von Goid: Lokaler Speicher

Mit dem Goid ist es sehr einfach, einen lokalen Goroutine-Speicher aufzubauen. Wir können ein GLS-Paket definieren, um die Goid-Funktion bereitzustellen:

package main

import "runtime"

func main() {
    var buf = make([]byte, 64)
    var stk = buf[:runtime.Stack(buf, false)]
    print(string(stk))
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Die gls-Paketvariable umschließt einfach eine Map und unterstützt den gleichzeitigen Zugriff über den sync.Mutex-Mutex.

Dann definieren Sie eine interne getMap-Funktion, um die Karte für jedes Goroutine-Byte zu erhalten:

goroutine 1 [running]:
main.main()
    /path/to/main.g
Nach dem Login kopieren
Nach dem Login kopieren

Nachdem Sie die private Karte der Goroutine erhalten haben, ist sie die normale Schnittstelle für Hinzufügungs-, Lösch- und Änderungsvorgänge:

import (
    "fmt"
    "strconv"
    "strings"
    "runtime"
)

func GetGoid() int64 {
    var (
        buf [64]byte
        n   = runtime.Stack(buf[:], false)
        stk = strings.TrimPrefix(string(buf[:n]), "goroutine")
    )

    idField := strings.Fields(stk)[0]
    id, err := strconv.Atoi(idField)
    if err!= nil {
        panic(fmt.Errorf("can not get goroutine id: %v", err))
    }

    return int64(id)
}
Nach dem Login kopieren
Nach dem Login kopieren

Schließlich stellen wir eine Clean-Funktion bereit, um die Kartenressourcen freizugeben, die der Goroutine entsprechen:

get_tls(CX)
MOVQ g(CX), AX     // Move g into AX.
Nach dem Login kopieren
Nach dem Login kopieren

Auf diese Weise wird ein minimalistisches Goroutine-GLS-Objekt für den lokalen Speicher fertiggestellt.

Das Folgende ist ein einfaches Beispiel für die Verwendung von lokalem Speicher:

#ifdef GOARCH_amd64
#define        get_tls(r)        MOVQ TLS, r
#define        g(r)        0(r)(TLS*1)
#endif
Nach dem Login kopieren
Nach dem Login kopieren

Durch den lokalen Speicher von Goroutine können verschiedene Funktionsebenen Speicherressourcen gemeinsam nutzen. Um gleichzeitig Ressourcenlecks zu vermeiden, muss in der Root-Funktion von Goroutine die Funktion gls.Clean() über die Defer-Anweisung aufgerufen werden, um Ressourcen freizugeben.

Leapcell: Die fortschrittliche serverlose Plattform zum Hosten von Golang-Anwendungen

How to Get the Goroutine ID?

Lassen Sie mich abschließend die am besten geeignete Plattform für die Bereitstellung von Go-Diensten empfehlen: leapcell

1. Mehrsprachige Unterstützung

  • Entwickeln Sie mit JavaScript, Python, Go oder Rust.

2. Stellen Sie unbegrenzt viele Projekte kostenlos bereit

  • Zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.

3. Unschlagbare Kosteneffizienz

  • Pay-as-you-go ohne Leerlaufgebühren.
  • Beispiel: 25 $ unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.

4. Optimierte Entwicklererfahrung

  • Intuitive Benutzeroberfläche für mühelose Einrichtung.
  • Vollautomatische CI/CD-Pipelines und GitOps-Integration.
  • Echtzeitmetriken und Protokollierung für umsetzbare Erkenntnisse.

5. Mühelose Skalierbarkeit und hohe Leistung

  • Automatische Skalierung zur problemlosen Bewältigung hoher Parallelität.
  • Kein Betriebsaufwand – konzentrieren Sie sich nur auf das Bauen.

Erfahren Sie mehr in der Dokumentation!

Leapcell Twitter: https://x.com/LeapcellHQ

Das obige ist der detaillierte Inhalt vonWie erhalte ich die Goroutine-ID?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
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