Concurrency trong Golang: Hiểu sâu về Goroutine leak và cách phòng tránh bằng context.Context
Goroutine siêu nhẹ nhưng không phải miễn phí Một trong những tính năng tuyệt vời nhất của Golang là Goroutine: Chỉ tiêu tốn khoảng 2KB bộ nhớ ngăn xếp stack, cho phép bạn tạo hàng trăm ngàn goroutine…
☰ Mục lục bài viết (5 phần) Mở / Đóng mục lục
Goroutine siêu nhẹ nhưng không phải miễn phí#
Một trong những tính năng tuyệt vời nhất của Golang là Goroutine: Chỉ tiêu tốn khoảng 2KB bộ nhớ ngăn xếp (stack), cho phép bạn tạo hàng trăm ngàn goroutine chạy đồng thời mà không làm sập server.
Tuy nhiên, Goroutine không được thu gom rác tự động nếu nó bị treo vĩnh viễn (Blocked). Hiện tượng này gọi là Goroutine Leak (Rò rỉ Goroutine) – nguyên nhân số 1 khiến các dịch vụ viết bằng Go bị phình RAM sau vài tuần chạy production.
Ví dụ kinh điển gây Goroutine Leak#
// LỖI NGUY HIỂM: Goroutine bị treo vô tận khi đọc channel không có buffer
func queryFastestServer() string {
ch := make(chan string) // Unbuffered channel!
go func() { ch <- queryServer("server-A") }()
go func() { ch <- queryServer("server-B") }()
return <-ch // Nhận kết quả từ server nhanh nhất và thoát hàm
}
Sau khi hàm queryFastestServer() thoát, server chậm hơn vẫn tiếp tục chạy và cố gắng đẩy kết quả vào ch. Nhưng vì không còn ai lắng nghe ch, goroutine thứ hai sẽ bị khóa vĩnh viễn trong bộ nhớ cho tới khi tắt server!
Giải pháp: Sử dụng context.WithTimeout chuẩn mực#
func queryWithTimeout(ctx context.Context) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
ch := make(chan string, 1) // Buffered channel dung lượng 1 để tránh block!
go func() {
result := doLongRunningTask()
select {
case ch <- result:
case <-ctx.Done(): // Hủy bỏ nếu đã quá hạn timeout!
log.Println("Task canceled, exiting goroutine cleanly")
return
}
}()
select {
case res := <-ch:
return res, nil
case <-ctx.Done():
return "", ctx.Err() // Trả về context deadline exceeded
}
}
2 Quy tắc sống còn khi viết Goroutine#
- Luôn biết chính xác khi nào và làm sao một goroutine sẽ kết thúc: Đừng bao giờ khởi chạy một goroutine mà không có lối thoát an toàn.
- Luôn truyền
context.Contextlàm tham số đầu tiên: Cho phép hủy bỏ tác vụ dây chuyền từ tầng HTTP request xuống tới tầng Database query.
Góc thảo luận#
Anh em dùng công cụ gì để phát hiện Goroutine Leak? Đã từng dùng runtime.NumGoroutine() để đặt alert trên Grafana chưa?
Xem thêm nội dung liên quan:
Thảo luận
1 phản hồi
Tham gia thảo luận
Bài liên quan trong #Backend & Microservices
-
Áp dụng Model Context Protocol (MCP) của Anthropic để kết nối AI với Postgres và Docker
Hoàng Nam (Tech Lead) · 💬 1 -
So sánh mô hình Embedding đa ngữ: OpenAI text-embedding-3 vs BGE-M3 vs Cohere
Minh Trí (AI Engineer) · 💬 1 -
Xây dựng Voice AI Agent thời gian thực với WebRTC và Whisper Live: Độ trễ dưới 500ms
Lan Anh (Senior Frontend) · 💬 1 -
Cấu hình Rate Limiting ở tầng Nginx và API Gateway: Chống DDOS Layer 7 hiệu quả
Đức Huy (DevOps Specialist) · 💬 1 -
Tokenization tiếng Việt của các LLM: Tại sao chi phí tiếng Việt đắt gấp 3 lần tiếng Anh?
Minh Trí (AI Engineer) · 💬 1