Rumah pembangunan bahagian belakang Golang Bagaimana untuk mendapatkan ID Goroutine?

Bagaimana untuk mendapatkan ID Goroutine?

Jan 04, 2025 am 10:45 AM

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")
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Selepas dijalankan, maklumat berikut akan dikeluarkan:

panic: leapcell

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

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")
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Selepas dijalankan, maklumat berikut akan dikeluarkan:

panic: leapcell

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

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))
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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
Salin selepas log masuk
Salin selepas log masuk

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)
}
Salin selepas log masuk
Salin selepas log masuk

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.
Salin selepas log masuk
Salin selepas log masuk

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
Salin selepas log masuk
Salin selepas log masuk

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

MOVQ TLS, CX
MOVQ 0(CX)(TLS*1), AX
Salin selepas log masuk

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

MOVQ (TLS), AX
Salin selepas log masuk

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
}
Salin selepas log masuk
-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")
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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))
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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

package main

func main() {
    panic("leapcell")
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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))
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

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
Salin selepas log masuk
Salin selepas log masuk

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)
}
Salin selepas log masuk
Salin selepas log masuk

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.
Salin selepas log masuk
Salin selepas log masuk

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
Salin selepas log masuk
Salin selepas log masuk

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 Laman Web ini
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

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

<🎜>: Bubble Gum Simulator Infinity - Cara Mendapatkan dan Menggunakan Kekunci Diraja
4 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Sistem Fusion, dijelaskan
4 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers of the Witch Tree - Cara Membuka Kunci Cangkuk Bergelut
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Tutorial Java
1671
14
Tutorial PHP
1276
29
Tutorial C#
1256
24
Golang vs Python: Prestasi dan Skala Golang vs Python: Prestasi dan Skala Apr 19, 2025 am 12:18 AM

Golang lebih baik daripada Python dari segi prestasi dan skalabiliti. 1) Ciri-ciri jenis kompilasi Golang dan model konkurensi yang cekap menjadikannya berfungsi dengan baik dalam senario konvensional yang tinggi. 2) Python, sebagai bahasa yang ditafsirkan, melaksanakan perlahan -lahan, tetapi dapat mengoptimumkan prestasi melalui alat seperti Cython.

Golang dan C: Konvensyen vs kelajuan mentah Golang dan C: Konvensyen vs kelajuan mentah Apr 21, 2025 am 12:16 AM

Golang lebih baik daripada C dalam kesesuaian, manakala C lebih baik daripada Golang dalam kelajuan mentah. 1) Golang mencapai kesesuaian yang cekap melalui goroutine dan saluran, yang sesuai untuk mengendalikan sejumlah besar tugas serentak. 2) C Melalui pengoptimuman pengkompil dan perpustakaan standard, ia menyediakan prestasi tinggi yang dekat dengan perkakasan, sesuai untuk aplikasi yang memerlukan pengoptimuman yang melampau.

Bermula dengan Go: Panduan Pemula Bermula dengan Go: Panduan Pemula Apr 26, 2025 am 12:21 AM

GoisidealforbeginnersandSuekableforcloudandnetworkservicesduetoitssimplicity, kecekapan, danconcurrencyfeatures.1) installgofromtheofficialwebsiteandverifywith'goversion'.2)

Golang vs C: Perbandingan Prestasi dan Kelajuan Golang vs C: Perbandingan Prestasi dan Kelajuan Apr 21, 2025 am 12:13 AM

Golang sesuai untuk pembangunan pesat dan senario serentak, dan C sesuai untuk senario di mana prestasi ekstrem dan kawalan peringkat rendah diperlukan. 1) Golang meningkatkan prestasi melalui pengumpulan sampah dan mekanisme konvensional, dan sesuai untuk pembangunan perkhidmatan web yang tinggi. 2) C mencapai prestasi muktamad melalui pengurusan memori manual dan pengoptimuman pengkompil, dan sesuai untuk pembangunan sistem tertanam.

Impak Golang: Kelajuan, Kecekapan, dan Kesederhanaan Impak Golang: Kelajuan, Kecekapan, dan Kesederhanaan Apr 14, 2025 am 12:11 AM

Goimpactsdevelopmentpositivielythroughspeed, efficiency, andsimplicity.1) Speed: goCompilesquicklyandrunsefficiently, idealforlargeproject.2) Kecekapan: ITSComprehensivestandardlibraryraryrarexternaldependencies, enhingdevelyficiency.

Golang vs Python: Perbezaan dan Persamaan Utama Golang vs Python: Perbezaan dan Persamaan Utama Apr 17, 2025 am 12:15 AM

Golang dan Python masing -masing mempunyai kelebihan mereka sendiri: Golang sesuai untuk prestasi tinggi dan pengaturcaraan serentak, sementara Python sesuai untuk sains data dan pembangunan web. Golang terkenal dengan model keserasiannya dan prestasi yang cekap, sementara Python terkenal dengan sintaks ringkas dan ekosistem perpustakaan yang kaya.

Golang dan C: Perdagangan dalam prestasi Golang dan C: Perdagangan dalam prestasi Apr 17, 2025 am 12:18 AM

Perbezaan prestasi antara Golang dan C terutamanya ditunjukkan dalam pengurusan ingatan, pengoptimuman kompilasi dan kecekapan runtime. 1) Mekanisme pengumpulan sampah Golang adalah mudah tetapi boleh menjejaskan prestasi, 2) Pengurusan memori manual C dan pengoptimuman pengkompil lebih cekap dalam pengkomputeran rekursif.

Perlumbaan Prestasi: Golang vs C Perlumbaan Prestasi: Golang vs C Apr 16, 2025 am 12:07 AM

Golang dan C masing-masing mempunyai kelebihan sendiri dalam pertandingan prestasi: 1) Golang sesuai untuk kesesuaian tinggi dan perkembangan pesat, dan 2) C menyediakan prestasi yang lebih tinggi dan kawalan halus. Pemilihan harus berdasarkan keperluan projek dan tumpukan teknologi pasukan.

See all articles