Go chuyên sâu: Runtime, Hiệu năng và Hệ thống
Sê-ri 132 bài Go nâng cao — runtime, scheduler, GC, bộ nhớ, compiler, concurrency nâng cao, kiến trúc, mạng phân tán và vận hành. Mỗi bài đo THẬT bằng Go 1.23.
18/132 phần đã đăng
Lập trình Go
1
Mô hình GMP trong Go: 100.000 goroutine chạy trên chỉ 5 OS thread
Goroutine chạy ở đâu? Go dùng mô hình GMP — G (goroutine) ghép lên M (OS thread) qua P (processor). Bài này đo thật: 100.001 goroutine chạy trên chỉ 5 thread qua 10 processor, và vì sao đó là lý do Go tạo goroutine gần như miễn phí. Go 1.23.
22/09/2026
· 6 phút đọc
2
Work-stealing trong Go: vì sao 40 goroutine tạo từ một chỗ vẫn chạy hết 10 core
Bộ lập lịch Go dùng work-stealing: một P hết việc sẽ đánh cắp một nửa hàng đợi của P khác. Bài này đo thật 40 việc CPU tạo từ main chạy nhanh 6,5 lần khi bật 10 core, và schedtrace cho thấy idleprocs=0 — mọi core đều bận. Go 1.23.
22/09/2026
· 6 phút đọc
3
Đọc GODEBUG=schedtrace trong Go: giải mã trạng thái bộ lập lịch không cần thư viện
schedtrace là công cụ số một để nhìn vào bộ lập lịch Go. Bài này giải mã từng trường của một dòng SCHED thật, và theo dõi vòng đời một run qua bốn snapshot — từ lúc goroutine dồn vào hàng đợi cục bộ tới khi mọi core bận. Kèm cách đọc để chẩn đoán nút thắt. Go 1.23.
22/09/2026
· 6 phút đọc
4
Async preemption trong Go: vì sao một vòng lặp for{} từng treo cả chương trình
Trước Go 1.14, một goroutine chạy vòng lặp chặt không gọi hàm có thể độc chiếm processor mãi mãi. Bài này đo thật: cùng một for{} vô hạn, bật async preemption thì main quay lại được (exit 0), tắt thì treo tới timeout (exit 124). Kèm cơ chế cắt bằng tín hiệu. Go 1.23.
22/09/2026
· 6 phút đọc
5
Netpoller trong Go: vì sao 5000 kết nối chỉ tốn 16 OS thread
I/O mạng chặn goroutine mà không chặn OS thread — nhờ netpoller tích hợp epoll/kqueue vào scheduler. Bài này đo thật: 5000 goroutine chờ network Read chỉ dùng 16 thread, trong khi 200 goroutine chặn trong syscall thật ngốn 203 thread. Đây là cách Go giải bài C10k. Go 1.23.
22/09/2026
· 6 phút đọc
6
Sysmon: thread giám sát nền của Go, và cách nó cứu goroutine khỏi chết đói vì syscall
Sysmon là thread nền không gắn P, chạy suốt vòng đời chương trình Go. Đo thật: GOMAXPROCS=1, một goroutine kẹt syscall 320ms mà goroutine khác vẫn đếm được 103 triệu lần — vì sysmon giành lại P. Và 40 syscall blocking đẻ ra 43 OS thread. Go 1.23.
22/09/2026
· 7 phút đọc
7
Vì sao goroutine chỉ tốn 2KB: stack tăng trưởng động trong Go, đo tận bytes
Goroutine sinh ra với stack 2KB chứ không phải 8MB như OS thread. Đo thật: 100.000 goroutine chỉ 2051 bytes stack mỗi cái; khi đệ quy sâu, runtime cấp stack gấp đôi (tỉ lệ x2.00 chính xác) và copy toàn bộ khung — địa chỉ biến cục bộ nhảy vùng. Go 1.23.
22/09/2026
· 7 phút đọc
8
Chuyển ngữ cảnh goroutine rẻ hơn thread 130 lần: đo tận nanosecond
Vì sao spawn hàng nghìn goroutine chạy mượt còn hàng nghìn OS thread thì nghẹt? Đo thật: ping-pong qua channel tốn 164 ns/vòng và 0 cấp phát, còn ping-pong qua pipe giữa hai OS thread tốn 21465 ns/vòng — đắt hơn 130 lần. Vì một cái ở user-space, cái kia qua kernel. Go 1.23.
22/09/2026
· 7 phút đọc
9
GOMAXPROCS trong container: vì sao Go hiểu taskset nhưng bỏ qua --cpus, và cách sửa
Go đặt GOMAXPROCS theo số CPU nó nhìn thấy — nhưng trong Kubernetes, giới hạn CPU thường là CFS quota mà Go 1.23 không đọc. Đo thật: Go nhận biết affinity (taskset thành NumCPU 2) nhưng khi GOMAXPROCS thừa so với CPU thực, chuyển ngữ cảnh cưỡng bức tăng gấp 4 lần. Go 1.23.
22/09/2026
· 6 phút đọc
10
Hàng đợi chạy cục bộ và toàn cục trong Go: vì sao mỗi P có queue riêng 256 slot
Bộ lập lịch Go không dùng một hàng đợi chung. Mỗi P giữ hàng đợi cục bộ lock-free tối đa 256 goroutine, cộng một hàng đợi toàn cục có khoá cho phần tràn. Đo thật bằng schedtrace: local queue không bao giờ vượt 256, global phình tới hàng vạn nhưng không bị bỏ đói. Go 1.23.
22/09/2026
· 7 phút đọc
11
Escape analysis trong Go: đọc -gcflags=-m để biết biến ở stack hay heap
Trình biên dịch Go tự quyết biến nào ở lại stack (rẻ) và biến nào thoát lên heap (do GC quản). Đo thật: cùng logic, slice cỡ hằng số 0 cấp phát 0.23 ns, slice cỡ biến 128 byte 27.94 ns — chậm 120 lần. Cách đọc output -m và vì sao nó quan trọng. Go 1.23.
22/09/2026
· 7 phút đọc
12
Vì sao con trỏ thoát ra heap trong Go: bốn thủ phạm và cách đọc leaking param
Con trỏ không tự làm biến lên heap — nó chỉ buộc thoát khi biến sống lâu hơn hàm. Đo thật bốn mẫu kinh điển bằng gcflags=-m: trả con trỏ, lưu toàn cục, đóng gói interface, và leaking param. Con trỏ dùng nội bộ vẫn ở stack 0 cấp phát. Go 1.23.
22/09/2026
· 7 phút đọc
13
Allocator ba tầng của Go: mcache, mcentral, mheap và vì sao cấp phát nhỏ không cần khoá
Go cấp phát bộ nhớ qua ba tầng như tcmalloc: mcache mỗi P không khoá, mcentral có khoá, mheap khoá heap. Đo thật: cấp đối tượng nhỏ song song còn nhanh hơn một luồng (mcache per-P), cấp lớn song song chậm gấp 3 vì tranh khoá mheap. Và tiny allocator gộp đối tượng nhỏ. Go 1.23.
22/09/2026
· 6 phút đọc
14
Size classes và span trong Go: vì sao xin 33 byte lại tốn 48, và phí phạm nội bộ
Go làm tròn mọi cấp phát lên một trong ~68 lớp kích thước cố định. Đo thật: xin 9 byte tốn 16 (phí 44%), xin 33 tốn 48 (phí 31%), nhưng object khớp lớp (16, 32, 48) thì 0 phí. Cách span tổ chức bộ nhớ và cách thiết kế struct khớp lớp. Go 1.23.
22/09/2026
· 6 phút đọc
15
Tiny allocator của Go: gộp object nhỏ để một byte chỉ tốn một byte
Object tí hon rất phổ biến nhưng cấp mỗi cái một slot 8-16 byte quá phí. Tiny allocator gộp object dưới 16 byte không con trỏ vào chung khối 16B. Đo thật: object 1-byte nén xuống đúng 1.0 B mỗi cái (thắng 8 lần), nhưng cùng cỡ mà có con trỏ thì không được gộp. Go 1.23.
22/09/2026
· 6 phút đọc
16
Đo cấp phát bằng -benchmem: hai cái bẫy khiến benchmark Go nói dối 0 alloc
B/op và allocs/op là công cụ tối ưu bộ nhớ Go, nhưng dễ nói dối. Đo thật: cùng một hàm cấp 1024 byte, benchmark báo 0 alloc khi kết quả bị vứt (compiler xoá code), và 1 alloc khi gán vào sink. Cách viết benchmark cấp phát đáng tin và testing.AllocsPerRun. Go 1.23.
22/09/2026
· 6 phút đọc
17
Tái dùng buffer trong Go: mẫu [:0] và prealloc cắt allocation từ 10 xuống 0
Hai kỹ thuật giảm allocation không cần thư viện. Đo thật: tái dùng buffer với buf[:0] cắt 10 cấp phát xuống 0 (nhanh 3.3 lần); prealloc đúng capacity cắt 12 cấp phát xuống 1 (nhanh 2.5 lần). Cơ chế, khi nào dùng, và các bẫy chia sẻ backing array. Go 1.23.
22/09/2026
· 6 phút đọc
18
sync.Pool trong Go: cơ chế, bẫy làm nó chậm hơn, và vì sao GC dọn sạch nó
sync.Pool tái dùng object giữa các goroutine, nhưng không phải lúc nào cũng nhanh hơn. Đo thật: pool []byte khi buffer không thoát heap còn CHẬM hơn (compiler để stack + boxing); pool *bytes.Buffer khi object thật sự cấp phát thì nhanh 28%. Và GC dọn sạch pool sau hai chu kỳ. Go 1.23.
22/09/2026
· 6 phút đọc
Còn 114 phần nữa sẽ lần lượt được đăng.