Context Package
Được giới thiệu từ năm 2014, Context package vẫn là một thành phần then chốt trong lập trình Go, cho phép quản lý hiệu quả dữ liệu theo phạm vi yêu cầu, thời hạn và tín hiệu hủy bỏ. Khi hệ sinh thái Go tiếp tục phát triển, việc hiểu rõ ngữ nghĩa của Context package là vô cùng quan trọng để xây dựng phần mềm đáng tin cậy và dễ bảo trì.
Giới thiệu
Go cung cấp từ khóa go
để tạo goroutine, nhưng lại không có từ khóa hoặc hỗ trợ trực tiếp nào để kết thúc goroutine. Trong một dịch vụ thực tế, khả năng đặt thời gian chờ và kết thúc goroutine là rất quan trọng để duy trì sự ổn định và hoạt động của dịch vụ.
Context package được Go team cung cấp như một giải pháp cho vấn đề này, đã được viết và giới thiệu bởi Sameer Ajmani vào năm 2014 tại hội nghị Gotham Go. Bạn có thể tham khảo thêm về chủ đề này:
- Slide Deck: https://talks.golang.org/2014/gotham-context.slide#1
- Blog Post: https://blog.golang.org/context
Sử dụng Context
Tạo context khi có request đến server
Thời điểm tốt nhất để tạo Context là càng sớm càng tốt trong quá trình xử lý một request hoặc task. Làm việc với Context sớm trong chu kỳ phát triển sẽ buộc bạn phải thiết kế các API nhận Context làm tham số đầu tiên. Ngay cả khi bạn không chắc chắn 100% một hàm có cần Context hay không, việc xóa Context khỏi một vài hàm dễ hơn nhiều so với việc cố gắng thêm Context sau này.
Từ phiên bản Go 1.7, http.Request
đã chứa một context (đọc thêm).
Theo quy ước trong Go, tên biến ctx
thường được sử dụng cho tất cả các giá trị context. Vì Context
là một interface, không nên sử dụng con trỏ. Mọi hàm chấp nhận Context nên nhận bản sao riêng của giá trị interface.
|
|
Các lệnh gọi đi server bên ngoài nên chấp nhận một Context
Ý tưởng đằng sau điều này là các lệnh gọi cấp cao hơn cần cho các lệnh gọi cấp thấp hơn biết họ sẵn sàng chờ bao lâu. Một ví dụ tuyệt vời về điều này là với gói http và những thay đổi trong phiên bản 1.7 đối với phương thức Do
để tôn trọng timeouts trên một request.
|
|
Không lưu trữ Context bên trong một kiểu struct
Truyền Context một cách tường minh cho mỗi hàm cần nó.
Về cơ bản, bất kỳ hàm nào thực hiện I/O đều nên chấp nhận một giá trị Context làm tham số đầu tiên và tôn trọng mọi timeout hoặc deadline được cấu hình bởi người gọi.
|
|
Chuỗi các lệnh gọi hàm phải lan truyền Context giữa chúng
Đây là một quy tắc quan trọng vì Context dựa trên request hoặc task. Bạn muốn Context và mọi thay đổi được thực hiện đối với nó trong quá trình xử lý request hoặc task được lan truyền và tôn trọng.
|
|
Nếu một giá trị Context cấp cao nhất mới được tạo, bất kỳ thông tin Context hiện có nào từ một lệnh gọi cấp cao hơn liên quan đến request sẽ bị mất.
Thay thế Context bằng cách sử dụng WithCancel, WithDeadline, WithTimeout hoặc WithValue
Vì mỗi hàm có thể thêm/sửa Context cho nhu cầu cụ thể của chúng, và những thay đổi đó không ảnh hưởng đến bất kỳ hàm nào đã được gọi trước đó, Context sử dụng ngữ nghĩa giá trị. Điều này có nghĩa là bất kỳ thay đổi nào đối với giá trị Context sẽ tạo ra một giá trị Context mới, sau đó được truyền đi.
|
|
Điều cực kỳ quan trọng là bất kỳ hàm cancel nào được trả về từ hàm With phải được thực thi trước khi hàm đó trả về. Đây là lý do tại sao quy tắc là sử dụng từ khóa defer
ngay sau lệnh gọi With. Không làm như vậy sẽ gây ra rò rỉ bộ nhớ trong chương trình.
Khi một Context bị hủy, tất cả các Context được sinh ra từ nó cũng bị hủy
|
|
Khi hàm cancel
được gọi, tất cả mười goroutine sẽ được bỏ chặn và in ra rằng chúng đã bị hủy.
Điều này cũng cho thấy rằng cùng một Context có thể được truyền cho các hàm chạy trong các goroutine khác nhau. Một Context an toàn để sử dụng đồng thời bởi nhiều goroutine.
Không truyền Context là nil, ngay cả khi một hàm cho phép
Truyền một context TODO
nếu bạn không chắc chắn về việc sử dụng Context nào.
Bạn biết mình cần một Context nhưng không chắc nó sẽ đến từ đâu. Bạn biết mình không có trách nhiệm tạo Context cấp cao nhất, vì vậy việc sử dụng hàm Background
là không thể. Bạn cần một Context cấp cao nhất tạm thời cho đến khi tìm ra Context thực tế đến từ đâu. Đây là lúc bạn nên sử dụng hàm TODO
thay vì hàm Background
.
Chỉ sử dụng context values cho dữ liệu có phạm vi request di chuyển qua các process và API, không dùng để truyền các tham số tùy chọn cho hàm
Không sử dụng giá trị Context để truyền dữ liệu vào một hàm khi dữ liệu đó là bắt buộc để hàm thực thi code của nó thành công. Nói cách khác, một hàm phải có khả năng thực thi logic của nó với một giá trị Context trống. Trong trường hợp một hàm yêu cầu thông tin phải có trong Context, nếu thông tin đó bị thiếu, chương trình sẽ bị lỗi và báo hiệu ứng dụng ngừng hoạt động.
Theo nguyên tắc chung, bạn nên tuân theo thứ tự này khi di chuyển dữ liệu trong chương trình của mình:
- Truyền dữ liệu làm tham số hàm: Đây là cách rõ ràng nhất để di chuyển dữ liệu trong chương trình mà không cần ẩn nó.
- Truyền dữ liệu thông qua receiver: Nếu hàm cần dữ liệu không thể thay đổi signature, thì hãy sử dụng một phương thức và truyền dữ liệu thông qua receiver.