שיעורי מודול (5/5)
`context.Context`: ביטול ומועדים
החבילה context היא התקן דה-פקטו לריבוי
ביטול, מועדים וערכים מוגבלים להיקף לאורך שרשרת השיחות,
על פני מספר גורוטינים. כל חבילות ה"רשתות" הסטנדרטיות
(net/http, database/sql, os/exec) קבל context.Context בתור
הפרמטר הראשון שלהם.
הממשק
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Done(): ערוץ שנסגר כאשר ההקשר מבוטל.Err(): סיבה לביטול (context.Canceledאוcontext.DeadlineExceeded).Deadline(): מועד אחרון אופציונלי.Value(): חיפוש ערכים מוגבלים להיקף (השתמש במשורה).
ארבע הפונקציות הבסיסיות
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כל הטפסים הנגזרים מחזירים פונקציה cancel: תמיד קוראים לה
(באופן אידיאלי עם defer) כדי לשחרר משאבים פנימיים, גם אם
ההקשר כבר פג.
דפוס פסק זמן
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
}אם queryUser לוקח יותר מ-2 שניות, ctx.Done() סגור ו
הפונקציה חייבת לבטל את הפעולה.
כיבוד ההקשר: בחר ב- Done()
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
}
}
}הכלל: בכל מקום שהפונקציה שלך עשויה לחסום במשך זמן רב, זה חייב לטפל ב-ctx.Done(). אחרת ההקשר "מופעל" עבור כלום.
מוסכמה של פרמטר ראשון
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
// ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionalectxתמיד כפרמטר הראשון, בשםctx.- לעולם לא לשים את
Contextכשדה של מבנה (חריגים נדירים מאוד). - לעולם לא לעבור את
nil: השתמש ב-context.TODO()בתור בדל.
context.WithValue: במשורה
type userKey struct{}
ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }הנחיות:
- רק עבור נתונים בהיקף של בקשה (מזהה בקשה, מזהה מעקב, זהות משתמש).
- לעולם לא עבור פרמטרים של פונקציות: העבר אותם כרגיל.
- מפתחות מסוג מותאם אישית לא מיוצא** כדי למנוע התנגשויות.
ריבוי לאורך השרשרת
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 מבטל אוטומטית את r.Context() כאשר הלקוח
מתנתק או כאשר המטפל חוזר. על ידי הפצתו לכולם
שיחות במורד הזרם, כל עץ הפעולות מתבטל
מפל: אין עבודה מבוזבזת לאחר ניתוק.
נסה את זה
צור הקשר עם פסק זמן של שנייה אחת שנגזר מ-context.Background() וזכור להתקשר לביטול עם defer.
הצג רמז
`context.WithTimeout(parent, d)` מחזיר `(ctx, cancel)`; תמיד להתקשר לבטל.
הפתרון זמין לאחר 3 ניסיונות
השתמש ב-<-ctx.Done() בבחירה כדי להמתין לביטול ההקשר.
הצג רמז
`ctx.Done()` הוא ערוץ שנסגר כאשר ההקשר מתבטל.
הפתרון זמין לאחר 3 ניסיונות
לפי המוסכמה, לאן עובר הפרמטר ctx בחתימת פונקציה?
func Fetch(???, url string) ([]byte, error)לְסַכֵּם
context.Contextמפיץ ערכי ביטול, מועד אחרון והיקף.Background()/TODO()בתור השורש;WithCancel/WithTimeout/WithDeadlineלגזור.- תמיד
defer cancel()כדי לשחרר משאבים. - פונקציות "ארוכות" חייבות
selectב-ctx.Done()או להעביר ctx לחבילות סטנדרטיות. - אמנה:
ctxכפרמטר FIRST, לעולם לא בתוך מבנה. WithValueרק עבור נתונים בהיקף בקשות (מזהה בקשה, מזהה מעקב).- ב-
net/http,r.Context()בוטל בעת ניתוק הלקוח.