Maison > développement back-end > Golang > Comment obtenir l'identifiant Goroutine ?

Comment obtenir l'identifiant Goroutine ?

Mary-Kate Olsen
Libérer: 2025-01-04 10:45:35
original
213 Les gens l'ont consulté

How to Get the Goroutine ID?

Dans un système d'exploitation, chaque processus a un ID de processus unique et chaque thread a son propre ID de thread unique. De même, dans le langage Go, chaque Goroutine possède son propre identifiant de routine Go unique, qui est souvent rencontré dans des scénarios comme la panique. Bien que les Goroutines aient des identifiants inhérents, le langage Go ne fournit délibérément pas d'interface pour obtenir cet identifiant. Cette fois, nous tenterons d'obtenir l'identifiant Goroutine via le langage assembleur Go.

1. La conception officielle de ne pas avoir de goid (https://github.com/golang/go/issues/22770)

Selon les documents officiels pertinents, la raison pour laquelle le langage Go ne fournit délibérément pas de goid est pour éviter les abus. Parce que la plupart des utilisateurs, après avoir facilement obtenu le goid, écriront inconsciemment du code qui dépend fortement de goid dans la programmation ultérieure. Une forte dépendance à l'égard de goid rendra ce code difficile à porter et compliquera également le modèle concurrent. Dans le même temps, il peut y avoir un grand nombre de Goroutines dans le langage Go, mais il n'est pas facile de surveiller en temps réel quand chaque Goroutine est détruite, ce qui empêchera également le recyclage automatique des ressources qui dépendent de goid ( nécessitant un recyclage manuel). Cependant, si vous êtes un utilisateur du langage assembleur Go, vous pouvez complètement ignorer ces problèmes.

Remarque : Si vous obtenez le goid de force, vous pourriez avoir "honte" ?:
https://github.com/golang/go/blob/master/src/runtime/proc.go#L7120

2. Obtenir du goid dans Pure Go

Pour faciliter la compréhension, essayons d'abord d'obtenir le goid en Go pur. Bien que les performances d'obtention du goid en Go pur soient relativement faibles, le code a une bonne portabilité et peut également être utilisé pour tester et vérifier si le goid obtenu par d'autres méthodes est correct.

Chaque utilisateur de la langue Go doit connaître la fonction panique. L’appel de la fonction panic provoquera une exception Goroutine. Si la panique n'est pas gérée par la fonction de récupération avant d'atteindre la fonction racine de Goroutine, le runtime imprimera les informations pertinentes sur l'exception et la pile et quittera Goroutine.

Construisons un exemple simple pour générer le goid par panique :

package main

func main() {
    panic("leapcell")
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Après l'exécution, les informations suivantes seront affichées :

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

On peut deviner que le 1 dans la goroutine 1 [running] des informations de sortie Panic est le goid. Mais comment pouvons-nous obtenir les informations de sortie de panique dans le programme ? En fait, les informations ci-dessus ne sont qu'une description textuelle du cadre actuel de la pile d'appels de fonction. La fonction runtime.Stack fournit la fonction d'obtenir ces informations.

Reconstruisons un exemple basé sur la fonction runtime.Stack pour afficher le goid en affichant les informations du frame de pile actuel :

package main

func main() {
    panic("leapcell")
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Après l'exécution, les informations suivantes seront affichées :

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ainsi, il est facile d'analyser les informations goid à partir de la chaîne obtenue par runtime.Stack :

package main

import "runtime"

func main() {
    var buf = make([]byte, 64)
    var stk = buf[:runtime.Stack(buf, false)]
    print(string(stk))
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous ne détaillerons pas les détails de la fonction GetGoid. Il convient de noter que la fonction runtime.Stack peut non seulement obtenir les informations de pile du Goroutine actuel mais également les informations de pile de tous les Goroutines (contrôlées par le deuxième paramètre). Dans le même temps, la fonction net/http2.curGoroutineID du langage Go obtient le goid de la même manière.

3. Obtention du goid à partir de la structure g

Selon la documentation officielle du langage assembleur Go, le pointeur g de chaque structure Goroutine en cours d'exécution est stocké dans le stockage local TLS du thread système où se trouve le Goroutine en cours d'exécution. Nous pouvons d'abord obtenir le stockage local du thread TLS, puis obtenir le pointeur de la structure g à partir du TLS, et enfin extraire le goid de la structure g.

Ce qui suit consiste à obtenir le pointeur g en se référant à la macro get_tls définie dans le package d'exécution :

goroutine 1 [running]:
main.main()
    /path/to/main.g
Copier après la connexion
Copier après la connexion

Le get_tls est une fonction macro définie dans le fichier d'en-tête runtime/go_tls.h.

Pour la plateforme AMD64, la fonction macro get_tls est définie comme suit :

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)
}
Copier après la connexion
Copier après la connexion

Après avoir développé la fonction macro get_tls, le code pour obtenir le pointeur g est le suivant :

get_tls(CX)
MOVQ g(CX), AX     // Move g into AX.
Copier après la connexion
Copier après la connexion

En fait, TLS est similaire à l'adresse du stockage local du thread, et les données dans la mémoire correspondant à l'adresse sont le pointeur g. Nous pouvons être plus simples :

#ifdef GOARCH_amd64
#define        get_tls(r)        MOVQ TLS, r
#define        g(r)        0(r)(TLS*1)
#endif
Copier après la connexion
Copier après la connexion

Sur la base de la méthode ci-dessus, nous pouvons envelopper une fonction getg pour obtenir le pointeur g :

MOVQ TLS, CX
MOVQ 0(CX)(TLS*1), AX
Copier après la connexion

Ensuite, dans le code Go, obtenez la valeur de goid grâce au décalage du membre goid dans la structure g :

MOVQ (TLS), AX
Copier après la connexion

Ici, g_goid_offset est le décalage du membre goid. La structure g fait référence à runtime/runtime2.go.

Dans la version Go1.10, le décalage de goid est de 152 octets. Ainsi, le code ci-dessus ne peut s'exécuter correctement que dans les versions Go où le décalage goid est également de 152 octets. Selon l’oracle du grand Thompson, le dénombrement et la force brute sont la panacée à tous les problèmes difficiles. Nous pouvons également enregistrer les décalages goid dans un tableau, puis interroger le décalage goid en fonction du numéro de version Go.

Ce qui suit est le code amélioré :

// 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
}
Copier après la connexion
-8 MOVQ (TLS), AX MOVQ AX, ret+0(FP) RET

Maintenant, le décalage goid peut enfin s'adapter automatiquement aux versions linguistiques Go publiées.

4. Obtention de l'objet d'interface correspondant à la structure g

Bien que l'énumération et la force brute soient simples, elles ne prennent pas bien en charge les versions Go inédites en cours de développement. Nous ne pouvons pas connaître à l'avance le décalage du membre goid dans une certaine version en cours de développement.

S'il se trouve dans le package d'exécution, nous pouvons directement obtenir le décalage du membre via unsafe.OffsetOf(g.goid). Nous pouvons également obtenir le type de la structure g par réflexion, puis interroger le décalage d'un certain membre via le type. Étant donné que la structure g est un type interne, le code Go ne peut pas obtenir les informations de type de la structure g à partir de packages externes. Cependant, dans le langage assembleur Go, nous pouvons voir tous les symboles, donc théoriquement, nous pouvons également obtenir les informations de type de la structure g.

Une fois qu'un type est défini, le langage Go générera les informations de type correspondantes pour ce type. Par exemple, la structure g générera un identifiant type·runtime·g pour représenter les informations de type valeur de la structure g, ainsi qu'un identifiant type·*runtime·g pour représenter les informations de type pointeur. Si la structure g a des méthodes, alors les informations de type go.itab.runtime.g et go.itab.*runtime.g seront également générées pour représenter les informations de type avec des méthodes.

Si nous pouvons obtenir le type·runtime·g représentant le type de la structure g et le pointeur g, alors nous pouvons construire l'interface de l'objet g. Voici la fonction getg améliorée, qui renvoie l'interface de l'objet pointeur g :

package main

func main() {
    panic("leapcell")
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, le registre AX correspond au pointeur g, et le registre BX correspond au type de la structure g. Ensuite, la fonction runtime·convT2E est utilisée pour convertir le type en interface. Parce que nous n’utilisons pas le type pointeur de la structure g, l’interface renvoyée représente le type valeur de la structure g. Théoriquement, nous pouvons également construire une interface de type pointeur g, mais en raison des limitations du langage assembleur Go, nous ne pouvons pas utiliser l'identifiant type·*runtime·g.

Basé sur l'interface renvoyée par g, il est facile d'obtenir le goid :

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le code ci-dessus obtient directement le goid par réflexion. Théoriquement, tant que le nom de l'interface réfléchie et le membre goid ne changent pas, le code peut s'exécuter normalement. Après des tests réels, le code ci-dessus peut s'exécuter correctement dans les versions Go1.8, Go1.9 et Go1.10. Avec un certain optimisme, si le nom du type de structure g ne change pas et que le mécanisme de réflexion du langage Go ne change pas, il devrait également pouvoir s'exécuter dans les futures versions du langage Go.

Bien que la réflexion ait un certain degré de flexibilité, la performance de la réflexion a toujours été critiquée. Une idée améliorée consiste à obtenir le décalage du goid par réflexion, puis à obtenir le goid via le pointeur g et le décalage, de sorte que la réflexion ne doive être exécutée qu'une seule fois dans la phase d'initialisation.

Ce qui suit est le code d'initialisation de la variable g_goid_offset :

package main

import "runtime"

func main() {
    var buf = make([]byte, 64)
    var stk = buf[:runtime.Stack(buf, false)]
    print(string(stk))
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Après avoir obtenu le bon décalage de goid, obtenez le goid de la manière mentionnée précédemment :

package main

func main() {
    panic("leapcell")
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

À ce stade, notre idée d'implémentation pour obtenir le goid est suffisamment complète, mais le code assembleur présente encore de sérieux risques de sécurité.

Bien que la fonction getg soit déclarée comme un type de fonction qui interdit le fractionnement de pile avec l'indicateur NOSPLIT, la fonction getg appelle en interne la fonction runtime·convT2E plus complexe. Si la fonction runtime·convT2E rencontre un espace de pile insuffisant, elle peut déclencher des opérations de fractionnement de pile. Lorsque la pile est divisée, le GC déplacera les pointeurs de pile dans les paramètres de fonction, les valeurs de retour et les variables locales. Cependant, notre fonction getg ne fournit pas d'informations de pointeur pour les variables locales.

Ce qui suit est l'implémentation complète de la fonction getg améliorée :

panic: leapcell

goroutine 1 [running]:
main.main()
    /path/to/main.go:4 +0x40
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, NO_LOCAL_POINTERS signifie que la fonction n'a pas de variables de pointeur local. Dans le même temps, l'interface renvoyée est initialisée avec des valeurs nulles et une fois l'initialisation terminée, GO_RESULTS_INITIALIZED est utilisé pour informer le GC. Cela garantit que lorsque la pile est divisée, le GC peut gérer correctement les pointeurs dans les valeurs de retour et les variables locales.

5. Application de goid : stockage local

Avec le goid, il est très simple de construire un stockage local Goroutine. Nous pouvons définir un package gls pour fournir la fonctionnalité goid :

package main

import "runtime"

func main() {
    var buf = make([]byte, 64)
    var stk = buf[:runtime.Stack(buf, false)]
    print(string(stk))
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La variable du package gls enveloppe simplement une carte et prend en charge l'accès simultané via le mutex sync.Mutex.

Définissez ensuite une fonction interne getMap pour obtenir la carte pour chaque octet Goroutine :

goroutine 1 [running]:
main.main()
    /path/to/main.g
Copier après la connexion
Copier après la connexion

Après avoir obtenu la carte privée de la Goroutine, c'est l'interface normale pour les opérations d'ajout, de suppression et de modification :

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)
}
Copier après la connexion
Copier après la connexion

Enfin, nous fournissons une fonction Clean pour libérer les ressources cartographiques correspondant à la Goroutine :

get_tls(CX)
MOVQ g(CX), AX     // Move g into AX.
Copier après la connexion
Copier après la connexion

De cette façon, un objet gls de stockage local Goroutine minimaliste est complété.

Ce qui suit est un exemple simple d'utilisation du stockage local :

#ifdef GOARCH_amd64
#define        get_tls(r)        MOVQ TLS, r
#define        g(r)        0(r)(TLS*1)
#endif
Copier après la connexion
Copier après la connexion

Grâce au stockage local Goroutine, différents niveaux de fonctions peuvent partager des ressources de stockage. Dans le même temps, pour éviter les fuites de ressources, dans la fonction racine de Goroutine, la fonction gls.Clean() doit être appelée via l'instruction defer pour libérer les ressources.

Leapcell : la plate-forme avancée sans serveur pour l'hébergement d'applications Golang

How to Get the Goroutine ID?

Enfin, permettez-moi de vous recommander la plateforme la plus adaptée au déploiement des services Go : leapcell

1. Prise en charge multilingue

  • Développez avec JavaScript, Python, Go ou Rust.

2. Déployez gratuitement un nombre illimité de projets

  • payez uniquement pour l'utilisation – pas de demande, pas de frais.

3. Rentabilité imbattable

  • Payez à l'utilisation sans frais d'inactivité.
  • Exemple : 25 $ prend en charge 6,94 millions de requêtes avec un temps de réponse moyen de 60 ms.

4. Expérience de développeur rationalisée

  • Interface utilisateur intuitive pour une configuration sans effort.
  • Pipelines CI/CD entièrement automatisés et intégration GitOps.
  • Mesures et journalisation en temps réel pour des informations exploitables.

5. Évolutivité sans effort et hautes performances

  • Mise à l'échelle automatique pour gérer facilement une concurrence élevée.
  • Zéro frais opérationnels : concentrez-vous uniquement sur la construction.

Explorez-en davantage dans la documentation !

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

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