# Go Context Package 2026: การยกเลิก, Timeout และคำถามสัมภาษณ์งาน > เชี่ยวชาญ package context ของ Go สำหรับการยกเลิก, timeout และ request-scoped values คู่มือฉบับสมบูรณ์พร้อมตัวอย่างโค้ดและคำถามสัมภาษณ์ - Published: 2026-08-29 - Updated: 2026-08-29 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Package context ของ Go มอบกลไกมาตรฐานสำหรับการส่งต่อ deadline, สัญญาณยกเลิก และ request-scoped values ข้ามขอบเขต API ทุกแอปพลิเคชัน Go ระดับ production ใช้ context: HTTP handler, การ query database, การเรียก gRPC และ background worker ล้วนพึ่งพามันสำหรับ graceful shutdown และการจัดการ timeout > **สำคัญสำหรับการสัมภาษณ์** > > Context เป็นหนึ่งในหัวข้อที่ถูกถามบ่อยที่สุดในการสัมภาษณ์ Go ผู้สัมภาษณ์คาดหวังให้ผู้สมัครอธิบาย context propagation, แสดงการจัดการการยกเลิกที่ถูกต้อง และหลีกเลี่ยงข้อผิดพลาดทั่วไปเช่นการเก็บ context ใน struct ## Interface Context และสี่ Method ของมัน [Interface Context](https://pkg.go.dev/context) กำหนดสี่ method ที่ทุก implementation ของ context ต้องตอบสนอง การทำความเข้าใจ method เหล่านี้เป็นรากฐานสำหรับการใช้ context อย่างมีประสิทธิภาพ ```go // context_interface.go type Context interface { // Deadline returns the time when work should be canceled Deadline() (deadline time.Time, ok bool) // Done returns a channel that closes when the context is canceled Done() <-chan struct{} // Err returns the reason why Done was closed Err() error // Value returns the value associated with key, or nil Value(key any) any } ``` `Done()` คืนค่า channel แบบรับอย่างเดียว เมื่อ channel นี้ปิด ทุก goroutine ที่กำลังฟังมันจะได้รับสัญญาณทันที Pattern `<-ctx.Done()` จะ block จนกว่าจะเกิดการยกเลิก `Err()` อธิบายเหตุผล: อาจเป็น `context.Canceled` เมื่อถูกยกเลิกอย่างชัดเจน หรือ `context.DeadlineExceeded` เมื่อ timeout หรือ deadline ผ่านไปแล้ว ## การสร้าง Context ด้วย Background และ TODO สองฟังก์ชันสร้าง root context: `context.Background()` และ `context.TODO()` ทั้งคู่คืนค่า context ที่ไม่เป็น nil, ว่างเปล่า และไม่เคยถูกยกเลิก ```go // context_creation.go package main import ( "context" "log" "net/http" ) func main() { // Background: the root context for your application ctx := context.Background() // Use it as parent for derived contexts server := &http.Server{Addr: ":8080"} // TODO: placeholder when context source is unclear // Static analysis tools can flag context.TODO() for review processLegacyData(context.TODO()) } func processLegacyData(ctx context.Context) { // Context parameter enables future cancellation support log.Println("Processing data...") } ``` `Background()` ทำหน้าที่เป็น parent สำหรับ incoming request, ฟังก์ชัน main และโค้ดเริ่มต้น `TODO()` ทำหน้าที่เป็น placeholder ระหว่างการ refactor เมื่อแหล่งที่มาของ context ที่ถูกต้องยังไม่ถูกกำหนด เครื่องมือวิเคราะห์แบบ static สามารถทำเครื่องหมายการใช้ `TODO()` เพื่อตรวจสอบในภายหลัง ## การยกเลิกด้วย WithCancel และ WithCancelCause การยกเลิกแบบ manual ช่วยให้ goroutine แม่ส่งสัญญาณไปยัง goroutine ลูกว่างานควรหยุด Pattern นี้ปรากฏใน worker pool, background task และการ implement graceful shutdown ```go // cancellation.go package main import ( "context" "errors" "fmt" "time" ) func worker(ctx context.Context, id int, results chan<- int) { for { select { case <-ctx.Done(): // Check why cancellation occurred if cause := context.Cause(ctx); cause != nil { fmt.Printf("Worker %d stopped: %v\n", id, cause) } return default: // Simulate work time.Sleep(100 * time.Millisecond) results <- id * 10 } } } func main() { // WithCancelCause provides error context ctx, cancel := context.WithCancelCause(context.Background()) results := make(chan int, 10) // Start 3 workers for i := 1; i <= 3; i++ { go worker(ctx, i, results) } // Collect some results for i := 0; i < 5; i++ { fmt.Println("Result:", <-results) } // Cancel with a specific reason cancel(errors.New("shutdown requested by user")) // context.Cause retrieves the cancellation reason time.Sleep(50 * time.Millisecond) fmt.Println("Cause:", context.Cause(ctx)) } ``` `WithCancelCause` ซึ่งเปิดตัวใน Go 1.20 ให้ข้อมูล error ที่ละเอียดกว่า `WithCancel` ธรรมดา ฟังก์ชัน `context.Cause()` ดึง error ที่ส่งไปยังฟังก์ชัน cancel ทำให้การ debug ง่ายขึ้นในระบบที่ซับซ้อน ## Timeout และ Deadline สำหรับการจัดการ Request HTTP handler, การเรียก database และ request API ภายนอกควรมี timeout เสมอ Package context มี `WithTimeout` และ `WithDeadline` เพื่อจุดประสงค์นี้ ทั้งคู่สร้าง context ที่ยกเลิกอัตโนมัติเมื่อหมดเวลา ```go // timeouts.go package main import ( "context" "fmt" "time" ) // fetchFromAPI simulates an HTTP call func fetchFromAPI(ctx context.Context, endpoint string) (string, error) { // Simulate variable response time responseTime := time.Duration(100+endpoint[0]%150) * time.Millisecond select { case <-time.After(responseTime): return fmt.Sprintf("Response from %s", endpoint), nil case <-ctx.Done(): return "", ctx.Err() } } func main() { // WithTimeout: relative duration ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() // Always call cancel to release resources result, err := fetchFromAPI(ctx, "api.example.com/users") if err != nil { if err == context.DeadlineExceeded { fmt.Println("Request timed out") } else { fmt.Println("Request canceled") } return } fmt.Println(result) // WithDeadline: absolute time deadline := time.Now().Add(500 * time.Millisecond) ctx2, cancel2 := context.WithDeadline(context.Background(), deadline) defer cancel2() result2, _ := fetchFromAPI(ctx2, "api.example.com/orders") fmt.Println(result2) } ``` Pattern `defer cancel()` รับประกันว่า resource จะถูกปล่อยแม้เมื่อ operation เสร็จก่อน timeout การข้าม call นี้ทำให้เกิด resource leak: goroutine timer ของ context ยังคงทำงานจนกว่า timeout จะหมดอายุ ## Request-Scoped Values ด้วย WithValue `WithValue` แนบข้อมูล request-scoped เข้ากับ context Use case ทั่วไปรวมถึง request ID, token การยืนยันตัวตน และ tracing span [เอกสารอย่างเป็นทางการ](https://pkg.go.dev/context#WithValue) เน้นการใช้ type ที่กำหนดเองเป็น key เพื่อหลีกเลี่ยงการชน ```go // context_values.go package main import ( "context" "fmt" "log" "net/http" "time" ) // Define custom key types to avoid collisions type contextKey string const ( requestIDKey contextKey = "requestID" userIDKey contextKey = "userID" ) // middleware adds request ID to context func requestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestID := r.Header.Get("X-Request-ID") if requestID == "" { requestID = generateRequestID() } // Create new context with request ID ctx := context.WithValue(r.Context(), requestIDKey, requestID) next.ServeHTTP(w, r.WithContext(ctx)) }) } // handler retrieves values from context func handler(w http.ResponseWriter, r *http.Request) { requestID, ok := r.Context().Value(requestIDKey).(string) if !ok { requestID = "unknown" } log.Printf("[%s] Processing request", requestID) fmt.Fprintf(w, "Request ID: %s", requestID) } func generateRequestID() string { return fmt.Sprintf("req-%d", time.Now().UnixNano()) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", handler) http.ListenAndServe(":8080", requestIDMiddleware(mux)) } ``` Context value สร้างห่วงโซ่ที่ไม่เปลี่ยนแปลง: การเรียก `WithValue` แต่ละครั้งสร้าง context ใหม่ที่ห่อหุ้ม parent การค้นหาจะเดินทางผ่านห่วงโซ่นี้ ทำให้ value ที่ซ้อนลึกเข้าถึงช้าลง เก็บเฉพาะข้อมูล request-scoped ไม่ใช่การกำหนดค่าแอปพลิเคชันหรือพารามิเตอร์ทางเลือก ## Utility WithoutCancel และ AfterFunc Go 1.21 เพิ่ม `WithoutCancel` สำหรับ operation ที่ต้องเสร็จสมบูรณ์โดยไม่คำนึงถึงการยกเลิก parent เช่น cleanup task `AfterFunc` กำหนดเวลา callback เมื่อ context ถูกยกเลิก ```go // utilities.go package main import ( "context" "fmt" "log" "time" ) func saveAuditLog(ctx context.Context, message string) error { // Use WithoutCancel: audit logs must complete even if request canceled cleanCtx := context.WithoutCancel(ctx) // Simulate database write select { case <-time.After(50 * time.Millisecond): log.Printf("Audit: %s", message) return nil case <-cleanCtx.Done(): // This branch never executes: WithoutCancel context is never canceled return cleanCtx.Err() } } func main() { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) // AfterFunc: register cleanup when context ends stop := context.AfterFunc(ctx, func() { fmt.Println("Context ended, running cleanup") }) // Perform operation time.Sleep(50 * time.Millisecond) // Cancel stops the AfterFunc from running if called before context ends if stop() { fmt.Println("Cleanup prevented") } cancel() // Audit log completes despite parent cancellation saveAuditLog(ctx, "Operation completed") } ``` `WithoutCancel` รักษา value จาก context parent แต่ละเลยสัญญาณยกเลิกของมัน Pattern นี้เหมาะสำหรับ logging, การส่ง metric และ database commit ที่ต้องสำเร็จโดยไม่คำนึงถึงผลลัพธ์ของ request ## คำถามและคำตอบสัมภาษณ์ การเตรียมตัวสำหรับการสัมภาษณ์เทคนิค Go ต้องการความเข้าใจเกี่ยวกับภายใน context และ best practice คำถามเหล่านี้ปรากฏบ่อยในการสัมภาษณ์ที่บริษัทที่ใช้ Go สำหรับบริการ backend สำหรับการเตรียมสัมภาษณ์ Go เพิ่มเติม ดูโมดูล [คำถามสัมภาษณ์ Context Package](/technologies/go/interview-questions/context-package) ### ทำไม context ควรเป็นพารามิเตอร์แรก? ทีม Go กำหนดข้อตกลงนี้เพื่อทำให้ context propagation ชัดเจนและสอดคล้อง การวาง context ไว้ตำแหน่งแรกส่งสัญญาณให้ผู้อ่านรู้ว่าฟังก์ชันเคารพการยกเลิก Library มาตรฐานปฏิบัติตาม pattern นี้: `http.Request.Context()`, `database/sql.QueryContext()` และ `grpc.UnaryInterceptor` ล้วนคาดหวัง context เป็นอาร์กิวเมนต์แรก ### เกิดอะไรขึ้นถ้าเก็บ context ใน struct? การเก็บ context ละเมิดโมเดล lifecycle ของ request struct อาจมีอายุยืนกว่า request ที่สร้างมัน ทำให้ operation ใช้สัญญาณยกเลิกที่ล้าสมัยหรือพลาด deadline ใหม่ เอกสาร context เตือนอย่างชัดเจนต่อ pattern นี้ ```go // BAD: context stored in struct type Service struct { ctx context.Context // Never do this } // GOOD: pass context to each method type Service struct{} func (s *Service) Process(ctx context.Context, data []byte) error { // Context flows through the call chain return s.save(ctx, data) } ``` ### จะจัดการ context ใน goroutine อย่างไร? Goroutine ต้องตรวจสอบ `ctx.Done()` ในลูปหลักหรือ statement select ของมัน การละเลย channel done สร้างการรั่วไหลของ goroutine: ฟังก์ชัน parent return แต่ goroutine ยังคงใช้ resource ต่อไป ```go // Correct pattern for long-running goroutines func processStream(ctx context.Context, stream <-chan Data) { for { select { case <-ctx.Done(): log.Println("Shutting down processor") return case data, ok := <-stream: if !ok { return } handle(data) } } } ``` ### เมื่อไหร่ควรใช้ context.TODO()? ใช้ `TODO()` ระหว่างการ refactoring ทีละขั้นเมื่อเพิ่มการสนับสนุน context ให้กับโค้ด legacy มันทำเครื่องหมายตำแหน่งที่ต้องการ context propagation ที่เหมาะสม โค้ด production ในที่สุดควรแทนที่การเรียก `TODO()` ทั้งหมดด้วย context จริงจาก request handler หรือการเริ่มต้นแอปพลิเคชัน ## ข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยง การใช้ context ผิดทำให้เกิด bug ที่ละเอียดอ่อนซึ่งปรากฏขึ้นภายใต้ภาระหนักหรือระหว่างลำดับ shutdown การจดจำ pattern เหล่านี้ป้องกันเหตุการณ์ใน production ```go // mistakes.go package main import ( "context" "time" ) // MISTAKE 1: Ignoring context cancellation func badWorker(ctx context.Context) { for { // Missing select on ctx.Done() doExpensiveWork() // Never stops when context canceled } } // MISTAKE 2: Not calling cancel func leakyTimeout() { ctx, _ := context.WithTimeout(context.Background(), time.Second) // Timer goroutine leaks until timeout expires _ = ctx } // MISTAKE 3: Using string keys for values func collisionProne(ctx context.Context) context.Context { // Different packages might use same string key return context.WithValue(ctx, "userID", 123) // Bad } // CORRECT versions func goodWorker(ctx context.Context) { for { select { case <-ctx.Done(): return default: doExpensiveWork() } } } func properTimeout() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // Always release resources _ = ctx } type userIDKey struct{} func collisionSafe(ctx context.Context) context.Context { return context.WithValue(ctx, userIDKey{}, 123) // Good } func doExpensiveWork() {} ``` ## ประเด็นสำคัญสำหรับการใช้ Go Context ใน Production - ส่ง context เป็นพารามิเตอร์แรกไปยังฟังก์ชัน อย่าเก็บมันใน struct - defer `cancel()` เสมอหลังจาก `WithTimeout`, `WithDeadline` หรือ `WithCancel` เพื่อป้องกัน resource leak - ใช้ `WithCancelCause` และ `context.Cause()` เพื่อการ debug error ที่ดีขึ้นในห่วงโซ่การยกเลิกที่ซับซ้อน - ตรวจสอบ `<-ctx.Done()` ในลูป goroutine เพื่อเปิดใช้งาน graceful shutdown และป้องกันการรั่วไหล - ใช้ type key ที่กำหนดเองสำหรับ `WithValue` เพื่อหลีกเลี่ยงการชนระหว่าง package - ใช้ `WithoutCancel` สำหรับ operation cleanup ที่ต้องเสร็จสมบูรณ์โดยไม่คำนึงถึงการยกเลิก parent - เลือก `context.Background()` สำหรับการเริ่มต้นแอปพลิเคชัน และ `context.TODO()` เฉพาะระหว่างการ refactoring --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/go/go-context-package-2026