Chuyển đến nội dung chính
Mô-đun 7 · Bài học 5 trong tổng số 535/50 trong khóa học~14 min
Bài học theo mô-đun (5/5)

`context.Context`: hủy bỏ và thời hạn

Gói context là tiêu chuẩn thực tế để truyền bá hủy, thời hạn và giá trị giới hạn phạm vi dọc theo chuỗi cuộc gọi, trên nhiều goroutines. Tất cả các gói "kết nối mạng" tiêu chuẩn (net/http, database/sql, os/exec) chấp nhận context.Context làm tham số đầu tiên của họ.

Giao diện

Go
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}
  • Done(): kênh bị đóng khi ngữ cảnh bị hủy.
  • Err(): lý do hủy (context.Canceled hoặc context.DeadlineExceeded).
  • Deadline(): thời hạn tùy chọn.
  • Value(): tra cứu các giá trị giới hạn phạm vi (sử dụng tiết kiệm).

Bốn chức năng cơ bản

Go
ctx := context.Background()              // radice: usalo in main, init, test
ctx := context.TODO()                    // "non so ancora": stub esplicito

ctx, cancel := context.WithCancel(parent)               // cancel manuale
ctx, cancel := context.WithTimeout(parent, d)           // scadenza relativa
ctx, cancel := context.WithDeadline(parent, t)          // scadenza assoluta

ctx := context.WithValue(parent, key, value)            // valore scope-bound

Tất cả các dạng dẫn xuất đều trả về hàm cancel: luôn gọi nó (lý tưởng nhất là với defer) để giải phóng tài nguyên nội bộ, ngay cả khi context đã hết hạn.

Mẫu thời gian chờ

Go
func fetchUser(id string) (*User, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    return queryUser(ctx, id)   // queryUser deve rispettare ctx
}

Nếu queryUser mất hơn 2 giây, ctx.Done() sẽ bị đóng và chức năng phải hủy bỏ hoạt động.

Tôn trọng bối cảnh: chọn Done()

Go
func slowWork(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()      // canceled o deadline exceeded
        case <-time.After(100 * time.Millisecond):
            // un tick di lavoro
        }
    }
}

Quy tắc: bất cứ nơi nào chức năng của bạn có thể bị chặn trong thời gian dài, nó sẽ phải xử lý ctx.Done(). Nếu không thì bối cảnh sẽ được "bật nguồn" cho không có gì.

Quy ước tham số đầu tiên

Go
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
//        ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionale
  • ctx LUÔN LUÔN làm tham số đầu tiên, được đặt tên là ctx.
  • Không bao giờ đặt Context làm trường của cấu trúc (rất hiếm trường hợp ngoại lệ).
  • Không bao giờ vượt qua nil: sử dụng context.TODO() làm sơ khai.

bối cảnh.WithValue: một cách tiết kiệm

Go
type userKey struct{}

ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }

Hướng dẫn:

  • CHỈ dành cho dữ liệu trong phạm vi yêu cầu (ID yêu cầu, ID theo dõi, danh tính người dùng).
  • KHÔNG BAO GIỜ cho các tham số hàm: truyền chúng một cách bình thường.
  • Khóa thuộc loại tùy chỉnh không xuất để tránh xung đột.

Tuyên truyền dọc theo chuỗi

Go
func handleHTTP(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()   // context legato al ciclo di vita della richiesta
    user, err := fetchUser(ctx, r.URL.Query().Get("id"))
    if err != nil { ... }
    // ...
}

net/http tự động hủy r.Context() khi khách hàng ngắt kết nối hoặc khi trình xử lý quay trở lại. Bằng cách truyền bá nó đến tất cả mọi người các cuộc gọi xuôi dòng, toàn bộ cây hoạt động bị hủy bỏ trong tầng: không lãng phí công việc sau khi ngắt kết nối.

Hãy thử nó

tập thể dục#go.m7.l5.e1
Nỗ lực: 0Đang tải…

Tạo bối cảnh có thời gian chờ 1 giây bắt nguồn từ context.Background() và nhớ gọi hủy bằng defer.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

`context.WithTimeout(parent, d)` trả về `(ctx, cancel)`; hủy cuộc gọi luôn.

Giải pháp khả dụng sau 3 lần thử

tập thể dục#go.m7.l5.e2
Nỗ lực: 0Đang tải…

Sử dụng <-ctx.Done() trong phần chọn để chờ ngữ cảnh bị hủy.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

`ctx.Done()` là một kênh đóng khi ngữ cảnh bị hủy.

Giải pháp khả dụng sau 3 lần thử

Quiz#go.m7.l5.e3
Sẵn sàng

Theo quy ước, tham số ctx nằm ở đâu trong chữ ký hàm?

Go
func Fetch(???, url string) ([]byte, error)
Tùy chọn trả lời

Tóm tắt lại

  • context.Context truyền bá các giá trị hủy, thời hạn và giới hạn phạm vi.
  • Background()/TODO() làm gốc; WithCancel/WithTimeout/WithDeadline để rút ra.
  • Luôn defer cancel() để giải phóng tài nguyên.
  • Các hàm "Dài" phải select trên ctx.Done() hoặc chuyển ctx sang các gói tiêu chuẩn.
  • Quy ước: ctx làm tham số FIRST, không bao giờ nằm ​​trong cấu trúc.
  • WithValue chỉ dành cho dữ liệu trong phạm vi yêu cầu (ID yêu cầu, ID theo dõi).
  • Trong net/http, r.Context() bị hủy khi ngắt kết nối máy khách.