Rumah >pembangunan bahagian belakang >Golang >Bagaimana untuk mendapatkan ID Goroutine?

Bagaimana untuk mendapatkan ID Goroutine?

Mary-Kate Olsen
Mary-Kate Olsenasal
2025-01-04 10:45:35158semak imbas

How to Get the Goroutine ID?

Dalam sistem pengendalian, setiap proses mempunyai ID proses yang unik dan setiap urutan mempunyai ID urutan uniknya sendiri. Begitu juga, dalam bahasa Go, setiap Goroutine mempunyai ID rutin Go yang unik, yang sering ditemui dalam senario seperti panik. Walaupun Goroutines mempunyai ID yang wujud, bahasa Go sengaja tidak menyediakan antara muka untuk mendapatkan ID ini. Kali ini, kami akan cuba mendapatkan ID Goroutine melalui bahasa himpunan Go.

1. Reka Bentuk Rasmi Tidak Memiliki goid(https://github.com/golang/go/issues/22770)

Menurut bahan rasmi berkaitan, sebab bahasa Go sengaja tidak menyediakan goid adalah untuk mengelakkan penyalahgunaan. Kerana kebanyakan pengguna, selepas mudah mendapat goid, secara tidak sedar akan menulis kod yang sangat bergantung pada goid dalam pengaturcaraan seterusnya. Kebergantungan yang kuat pada goid akan menjadikan kod ini sukar untuk dipindahkan dan juga merumitkan model serentak. Pada masa yang sama, mungkin terdapat sejumlah besar Goroutine dalam bahasa Go, tetapi tidak mudah untuk memantau dalam masa nyata apabila setiap Goroutine dimusnahkan, yang juga akan menyebabkan sumber yang bergantung pada goid tidak dikitar semula secara automatik ( memerlukan kitar semula secara manual). Walau bagaimanapun, jika anda seorang pengguna bahasa himpunan Go, anda boleh mengabaikan sepenuhnya kebimbangan ini.

Nota: Jika anda mendapatkan goid secara paksa, anda mungkin akan "malu" ?:
https://github.com/golang/go/blob/master/src/runtime/proc.go#L7120

2. Mendapatkan goid dalam Pure Go

Untuk memudahkan pemahaman, mari kita cuba mendapatkan goid dalam Go tulen. Walaupun prestasi mendapatkan goid dalam Go tulen agak rendah, kod tersebut mempunyai kemudahalihan yang baik dan juga boleh digunakan untuk menguji dan mengesahkan sama ada goid yang diperoleh melalui kaedah lain adalah betul.

Setiap pengguna bahasa Go harus mengetahui fungsi panik. Memanggil fungsi panik akan menyebabkan pengecualian Goroutine. Jika panik tidak dikendalikan oleh fungsi pulih sebelum mencapai fungsi akar Goroutine, masa jalan akan mencetak maklumat pengecualian dan tindanan yang berkaitan dan keluar dari Goroutine.

Mari kita bina contoh mudah untuk mengeluarkan goid melalui panik:

package main

func main() {
    panic("leapcell")
}

Selepas dijalankan, maklumat berikut akan dikeluarkan:

panic: leapcell

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

Kita boleh mengagak bahawa 1 dalam maklumat keluaran Panic goroutine 1 [berjalan] adalah goid. Tetapi bagaimana kita boleh mendapatkan maklumat output panik dalam program ini? Sebenarnya, maklumat di atas hanyalah penerangan teks bagi rangka timbunan panggilan fungsi semasa. Fungsi runtime.Stack menyediakan fungsi untuk mendapatkan maklumat ini.

Mari kita bina semula contoh berdasarkan masa jalan.Fungsi tindanan untuk mengeluarkan goid dengan mengeluarkan maklumat bingkai tindanan semasa:

package main

func main() {
    panic("leapcell")
}

Selepas dijalankan, maklumat berikut akan dikeluarkan:

panic: leapcell

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

Jadi, mudah untuk menghuraikan maklumat goid daripada rentetan yang diperolehi oleh runtime.Stack:

package main

import "runtime"

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

Kami tidak akan menghuraikan butiran fungsi GetGoid. Perlu diingatkan bahawa fungsi runtime.Stack bukan sahaja boleh mendapatkan maklumat tindanan Goroutine semasa tetapi juga maklumat tindanan semua Goroutines (dikawal oleh parameter kedua). Pada masa yang sama, fungsi net/http2.curGoroutineID dalam bahasa Go memperoleh goid dengan cara yang sama.

3. Mendapatkan goid daripada g Structure

Menurut dokumentasi bahasa perhimpunan rasmi Go, penunjuk g bagi setiap struktur Goroutine yang sedang berjalan disimpan dalam TLS storan setempat bagi urutan sistem di mana Goroutine yang sedang berjalan berada. Mula-mula kita boleh mendapatkan storan setempat benang TLS, kemudian mendapatkan penunjuk struktur g daripada TLS, dan akhirnya mengekstrak goid daripada struktur g.

Berikut adalah untuk mendapatkan penunjuk g dengan merujuk kepada makro get_tls yang ditakrifkan dalam pakej masa jalan:

goroutine 1 [running]:
main.main()
    /path/to/main.g

get_tls ialah fungsi makro yang ditakrifkan dalam fail pengepala runtime/go_tls.h.

Untuk platform AMD64, fungsi makro get_tls ditakrifkan seperti berikut:

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

Selepas mengembangkan fungsi makro get_tls, kod untuk mendapatkan penunjuk g adalah seperti berikut:

get_tls(CX)
MOVQ g(CX), AX     // Move g into AX.

Malah, TLS adalah serupa dengan alamat storan setempat benang, dan data dalam memori yang sepadan dengan alamat ialah penunjuk g. Kami boleh menjadi lebih mudah:

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

Berdasarkan kaedah di atas, kita boleh membungkus fungsi getg untuk mendapatkan penunjuk g:

MOVQ TLS, CX
MOVQ 0(CX)(TLS*1), AX

Kemudian, dalam kod Go, dapatkan nilai goid melalui offset ahli goid dalam struktur g:

MOVQ (TLS), AX

Di sini, g_goid_offset ialah offset ahli goid. Struktur g merujuk kepada runtime/runtime2.go.

Dalam versi Go1.10, ofset goid ialah 152 bait. Jadi, kod di atas hanya boleh berjalan dengan betul dalam versi Go yang mengimbangi goid juga ialah 152 bait. Menurut ramalan Thompson yang hebat, penghitungan dan kekerasan adalah penawar untuk semua masalah yang sukar. Kami juga boleh menyimpan offset goid dalam jadual dan kemudian menanyakan offset goid mengikut nombor versi Go.

Berikut ialah kod yang dipertingkatkan:

// 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
}
-8 MOVQ (TLS), AX MOVQ AX, ret+0(FP) RET

Kini, offset goid akhirnya boleh menyesuaikan diri secara automatik kepada versi bahasa Go yang dikeluarkan.

4. Mendapatkan Objek Antara Muka Sepadan dengan Struktur g

Walaupun penghitungan dan kekerasan adalah mudah, mereka tidak menyokong dengan baik versi Go yang belum dikeluarkan dalam pembangunan. Kami tidak dapat mengetahui terlebih dahulu offset ahli goid dalam versi tertentu yang sedang dibangunkan.

Jika ia berada di dalam pakej runtime, kami boleh terus mendapatkan offset ahli melalui unsafe.OffsetOf(g.goid). Kita juga boleh mendapatkan jenis struktur g melalui refleksi dan kemudian menanyakan offset ahli tertentu melalui jenis. Oleh kerana struktur g ialah jenis dalaman, kod Go tidak boleh mendapatkan maklumat jenis struktur g daripada pakej luaran. Walau bagaimanapun, dalam bahasa himpunan Go, kita boleh melihat semua simbol, jadi secara teorinya, kita juga boleh mendapatkan maklumat jenis struktur g.

Selepas sebarang jenis ditentukan, bahasa Go akan menjana maklumat jenis yang sepadan untuk jenis itu. Sebagai contoh, struktur g akan menjana pengecam jenis·runtime·g untuk mewakili maklumat jenis nilai struktur g, dan juga pengecam jenis·*runtime·g untuk mewakili maklumat jenis penunjuk. Jika struktur g mempunyai kaedah, maka maklumat jenis go.itab.runtime.g dan go.itab.*runtime.g juga akan dijana untuk mewakili maklumat jenis dengan kaedah.

Jika kita boleh mendapatkan jenis·runtime·g yang mewakili jenis struktur g dan penunjuk g, maka kita boleh membina antara muka objek g. Berikut ialah fungsi getg yang dipertingkatkan, yang mengembalikan antara muka objek penunjuk g:

package main

func main() {
    panic("leapcell")
}

Di sini, daftar AX sepadan dengan penunjuk g, dan daftar BX sepadan dengan jenis struktur g. Kemudian, fungsi runtime·convT2E digunakan untuk menukar jenis kepada antara muka. Oleh kerana kami tidak menggunakan jenis penunjuk struktur g, antara muka yang dikembalikan mewakili jenis nilai struktur g. Secara teorinya, kami juga boleh membina antara muka jenis penunjuk g, tetapi disebabkan oleh pengehadan bahasa himpunan Go, kami tidak boleh menggunakan pengecam jenis·*runtime·g.

Berdasarkan antara muka yang dikembalikan oleh g, mudah untuk mendapatkan goid:

panic: leapcell

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

Kod di atas secara langsung memperoleh goid melalui refleksi. Secara teorinya, selagi nama antara muka yang dicerminkan dan ahli goid tidak berubah, kod itu boleh berjalan seperti biasa. Selepas ujian sebenar, kod di atas boleh berjalan dengan betul dalam versi Go1.8, Go1.9 dan Go1.10. Secara optimistik, jika nama jenis struktur g tidak berubah dan mekanisme pantulan bahasa Go tidak berubah, ia juga sepatutnya dapat dijalankan dalam versi bahasa Go yang akan datang.

Walaupun refleksi mempunyai tahap fleksibiliti tertentu, prestasi refleksi sentiasa dikritik. Idea yang lebih baik adalah untuk mendapatkan offset goid melalui refleksi dan kemudian mendapatkan goid melalui penunjuk g dan ofset, supaya refleksi hanya perlu dilaksanakan sekali dalam fasa permulaan.

Berikut ialah kod permulaan untuk pembolehubah g_goid_offset:

package main

import "runtime"

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

Selepas mempunyai offset goid yang betul, dapatkan goid dengan cara yang dinyatakan sebelum ini:

package main

func main() {
    panic("leapcell")
}

Pada ketika ini, idea pelaksanaan kami untuk mendapatkan goid sudah cukup lengkap, tetapi kod pemasangan masih mempunyai risiko keselamatan yang serius.

Walaupun fungsi getg diisytiharkan sebagai jenis fungsi yang melarang pemisahan tindanan dengan bendera NOSPLIT, fungsi getg secara dalaman memanggil fungsi runtime·convT2E yang lebih kompleks. Jika fungsi runtime·convT2E menemui ruang tindanan yang tidak mencukupi, ia mungkin mencetuskan operasi pemisahan tindanan. Apabila tindanan dipecahkan, GC akan menggerakkan penunjuk tindanan dalam parameter fungsi, nilai pulangan dan pembolehubah setempat. Walau bagaimanapun, fungsi getg kami tidak menyediakan maklumat penunjuk untuk pembolehubah setempat.

Berikut ialah pelaksanaan lengkap fungsi getg yang dipertingkatkan:

panic: leapcell

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

Di sini, NO_LOCAL_POINTERS bermakna fungsi itu tidak mempunyai pembolehubah penunjuk setempat. Pada masa yang sama, antara muka yang dikembalikan dimulakan dengan nilai sifar dan selepas permulaan selesai, GO_RESULTS_INITIALIZED digunakan untuk memaklumkan GC. Ini memastikan bahawa apabila tindanan dipecahkan, GC boleh mengendalikan penunjuk dengan betul dalam nilai pulangan dan pembolehubah setempat.

5. Aplikasi goid: Storan Tempatan

Dengan goid, sangat mudah untuk membina storan tempatan Goroutine. Kami boleh menentukan pakej gls untuk menyediakan ciri goid:

package main

import "runtime"

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

Pembolehubah pakej gls hanya membungkus peta dan menyokong akses serentak melalui penyegerakan.Mutex mutex.

Kemudian tentukan fungsi getMap dalaman untuk mendapatkan peta bagi setiap bait Goroutine:

goroutine 1 [running]:
main.main()
    /path/to/main.g

Selepas mendapatkan peta peribadi Goroutine, ia adalah antara muka biasa untuk operasi penambahan, pemadaman dan pengubahsuaian:

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

Akhir sekali, kami menyediakan fungsi Bersih untuk mengeluarkan sumber peta yang sepadan dengan Goroutine:

get_tls(CX)
MOVQ g(CX), AX     // Move g into AX.

Dengan cara ini, objek gls storan tempatan Goroutine minimalis selesai.

Berikut ialah contoh mudah menggunakan storan tempatan:

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

Melalui storan tempatan Goroutine, tahap fungsi yang berbeza boleh berkongsi sumber storan. Pada masa yang sama, untuk mengelakkan kebocoran sumber, dalam fungsi akar Goroutine, fungsi gls.Clean() perlu dipanggil melalui pernyataan penangguhan untuk mengeluarkan sumber.

Leapcell: Platform Tanpa Pelayan Termaju untuk Mengehos Aplikasi Golang

How to Get the Goroutine ID?

Akhir sekali, izinkan saya mengesyorkan platform yang paling sesuai untuk menggunakan perkhidmatan Go: leapcell

1. Sokongan Pelbagai Bahasa

  • Bangun dengan JavaScript, Python, Go atau Rust.

2. Sebarkan projek tanpa had secara percuma

  • bayar hanya untuk penggunaan — tiada permintaan, tiada caj.

3. Kecekapan Kos yang tiada tandingan

  • Bayar semasa anda pergi tanpa caj terbiar.
  • Contoh: $25 menyokong 6.94 juta permintaan pada purata masa tindak balas 60ms.

4. Pengalaman Pembangun Diperkemas

  • UI intuitif untuk persediaan yang mudah.
  • Saluran paip CI/CD automatik sepenuhnya dan penyepaduan GitOps.
  • Metrik masa nyata dan pengelogan untuk mendapatkan cerapan yang boleh diambil tindakan.

5. Kebolehskalaan dan Prestasi Tinggi yang Mudah

  • Penskalaan automatik untuk mengendalikan konkurensi tinggi dengan mudah.
  • Sifar operasi overhed — hanya fokus pada pembinaan.

Teroka lebih lanjut dalam dokumentasi!

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

Atas ialah kandungan terperinci Bagaimana untuk mendapatkan ID Goroutine?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn