Go context.Context — hiểu đúng để xài đúng
Hồi mới học Go, mình cứ nghĩ context là cái gì đó cao siêu lắm
Thiệt ra thì ban đầu mình cũng chỉ biết xài context.Background() cho có, chứ không hiểu tại sao function nào cũng bắt mình truyền context vô làm chi. Rồi một ngày đẹp trời, mình gặp cái bug mà server call API bên thứ 3 mà không có timeout — nó treo luôn cả request, không ai phục vụ được ai.
Ảnh: Brett Jordan — Pexels
Context làm được 3 chuyện chính
1. Timeout — ấn định giới hạn thời gian
Bạn gọi database, gọi API, hay đọc file — nếu tác vụ đó quá lâu, context sẽ tự động hủy. Chỉ cần một dòng:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
Xong. Quá 5 giây là context tự “báo hủy”, không cần canh đồng hồ thủ công.
2. Cancellation — chủ động ngưng tác vụ
Có những lúc mình muốn tự tay hủy — ví dụ user bấm nút Cancel, hoặc request đầu vào đã bị timeout rồi thì mấy tác vụ con cũng không cần chạy nữa.
ctx, cancel := context.WithCancel(parentCtx)
// Khi cần hủy:
cancel()
Mấy goroutine con chỉ cần select channel <-ctx.Done() là biết ngay.
Ảnh: ThisIsEngineering — Pexels
3. Pass giá trị — request-scoped values
Cái này hơi gây tranh cãi, vì nhiều người bảo dùng context để pass giá trị là anti-pattern. Mình thấy cũng đúng — nếu bạn pass cả tá thứ qua context thì code khó đọc vô cùng. Nhưng vài thứ “ngầm” như request ID, trace ID, user info thì context là chỗ hợp lý.
ctx := context.WithValue(ctx, "request_id", reqID)
// Bên dưới lấy ra:
id := ctx.Value("request_id")
Nguyên tắc vàng: context phải là tham số đầu tiên
Convention của Go là context luôn nằm ở tham số đầu tiên của function. Không giấu trong struct, không để global. Tại sao? Vì context đi kèm với request — mỗi request một context riêng, global thì mất hết ý nghĩa.
Với lại, nhớ luôn gọi cancel() khi dùng WithTimeout hay WithCancel. Nếu quên, context và goroutine của bạn sẽ sống dai hơn cả tuổi thọ của server — memory leak đó mấy bạn.
Nói chung, context là một trong những thứ làm Go khác biệt. Không phải “thêm 1 dòng cho vui” đâu — mỗi dòng context là một lớp bảo vệ server của bạn khỏi treo, timeout, và rác.
Có ai mới học Go mà cũng từng bối rối với context giống mình hông? :)
📋 Phụ lục thuật ngữ
- Context — gói thông tin về request (timeout, cancel, giá trị kèm theo) được truyền xuyên suốt call chain trong Go
- Goroutine — luồng nhẹ trong Go, chạy đồng thời với các goroutine khác
- Timeout — giới hạn thời gian tối đa cho một tác vụ
- Cancellation — khả năng hủy chủ động một tác vụ đang chạy