Context
context.Context คืออะไร?
หัวข้อที่มีชื่อว่า “context.Context คืออะไร?”context.Context เป็น standard interface ที่ carry:
- Cancellation signal — goroutine สามารถตรวจสอบ
ctx.Done()เพื่อรู้ว่าควรหยุดทำงาน - Deadline หรือ timeout ที่เป็น optional — งานถูก cancel อัตโนมัติเมื่อถึงเวลา
- Request-scoped key/value pairs — metadata ขนาดเล็กเช่น request ID หรือ auth token
Package context แก้ปัญหาที่เป็นรูปธรรม: เมื่อ server request ถูก cancel (user ปิด browser, upstream timeout) goroutines ที่รันสำหรับ request นั้นจะรู้ได้อย่างไรว่าควรหยุด? การส่ง context.Context เป็น argument แรกให้ทุก I/O function คือคำตอบมาตรฐานของ Go
การสร้าง Contexts
หัวข้อที่มีชื่อว่า “การสร้าง Contexts”// Root contexts — ไม่มี cancellationcontext.Background() // root ระดับบนสุด ใช้ใน main หรือ test setupcontext.TODO() // placeholder เมื่อยังไม่แน่ใจจะใช้ context ไหน
// Derived contexts — เพิ่ม cancellation/timeout บน parentctx, cancel := context.WithCancel(parent)ctx, cancel := context.WithTimeout(parent, 5*time.Second)ctx, cancel := context.WithDeadline(parent, time.Now().Add(5*time.Second))ctx2 := context.WithValue(parent, key, value)Derived contexts สร้าง tree การ cancel parent จะ cancel ลูกทั้งหมดอัตโนมัติ
Cancellation Contract
หัวข้อที่มีชื่อว่า “Cancellation Contract”เมื่อเรียก context.WithCancel หรือ context.WithTimeout คุณได้รับสองค่า: derived context และ cancel function คุณ ต้อง เรียก cancel() เมื่องานเสร็จ — แม้ context จะหมดอายุตามธรรมชาติ ถ้าไม่เรียก parent context จะถือ reference ถึง child context จนกว่าจะ cancel parent ด้วย ซึ่งทำให้ leak ทรัพยากร
ฟัง Cancellation
หัวข้อที่มีชื่อว่า “ฟัง Cancellation”ใน goroutine ctx.Done() return channel ที่ถูกปิดเมื่อ context ถูก cancel หรือ timeout ใช้ select เพื่อรวมกับงานจริง:
select {case result := <-workCh: return result, nilcase <-ctx.Done(): return "", ctx.Err() // context.Canceled หรือ context.DeadlineExceeded}package main
import ( "context" "fmt")
func fetchData(ctx context.Context, id int) (string, error) { select { case <-ctx.Done(): return "", ctx.Err() default: return fmt.Sprintf("data-%d", id), nil }}
type ctxKey string
func main() { // WithCancel: fetch ปกติสำเร็จ ctx, cancel := context.WithCancel(context.Background()) defer cancel()
result, err := fetchData(ctx, 1) if err != nil { fmt.Println("error:", err) } else { fmt.Println("got:", result) }
// Cancel แล้วลองอีกครั้ง — context เป็น done แล้ว cancel() result2, err2 := fetchData(ctx, 2) if err2 != nil { fmt.Println("cancelled:", err2) } else { fmt.Println("got:", result2) }
// WithValue: แนบ request-scoped metadata ctx3 := context.WithValue(context.Background(), ctxKey("requestID"), "abc-123") rid := ctx3.Value(ctxKey("requestID")).(string) fmt.Println("requestID:", rid)}Loading Go runtime (first run only, ~8 MB)…
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| context.WithCancel | propagate cancellation tree | ต้อง call cancel() เสมอ ไม่งั้น goroutine leak |
| context.WithTimeout | deadline อัตโนมัติ | ต้อง handle context.DeadlineExceeded |
| context.WithValue | pass value ผ่าน call stack | type unsafe, ใช้ unexported key เพื่อหลีกเลี่ยง collision |
| ctx.Done() channel | non-blocking check via select | ต้อง check ในทุก loop iteration ที่อาจรันนาน |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- context.WithValue เหมาะสำหรับ pass business data — ใช้สำหรับ request-scoped metadata เท่านั้น (trace ID, auth token)
- cancel() ทำให้ goroutine หยุดทันที — cancel() ส่ง signal ผ่าน channel — goroutine ต้องตรวจสอบ ctx.Done() เอง
- context ส่ง value ได้ทุก type — context.Value() คืน any ต้องใช้ type assertion และอาจ nil
💡 ตัวอย่างจากของจริง
Kubernetes ส่ง context จาก API server → controller → goroutine ทุกตัว เพื่อ graceful shutdown
net/http ทุก HTTP request มี context — client disconnect ยกเลิก context ทำให้ handler cleanup ได้
gRPC propagate deadline ผ่าน context ข้าม service boundary อัตโนมัติ