在 go 伺服器中,對於每個請求的 request 都是在單獨的 goroutine 中進行的,處理一個 request 也可能涉及多個 goroutine 之間的交互, 使用 context 可以使開發者方便的在這些 goroutine 裡傳遞 request 相關的數據、取消 goroutine 的 signal 或截止日期。
結構229 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {230 c := newCancelCtx(parent)231 propagateCancel(parent, &c)232 return &c, func() { c.cancel(true, Canceled) }233 }234 235 236 func newCancelCtx(parent Context) cancelCtx {237 return cancelCtx{238 Context: parent,239 done: make(chan struct{}),240 }241 }案例package main
import ( "context" "fmt")
func main() { fmt.Println("嗨客網(www.haicoder.net)")
gen := func(ctx context.Context) <-chan int { dst := make(chan int) n := 1 go func() { for { select { case <-ctx.Done(): return case dst <- n: n++ } } }() return dst }
ctx, cancel := context.WithCancel(context.Background()) defer cancel() for n := range gen(ctx) { fmt.Println("Received", n) if n == 5 { break } }}
運行後,如下圖所示:
結構369 func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {370 if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {371 372 return WithCancel(parent)373 }374 c := &timerCtx{375 cancelCtx: newCancelCtx(parent),376 deadline: deadline,377 }案例package main
import ( "context" "fmt" "time")
func main() { fmt.Println("嗨客網(www.haicoder.net)")
d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println("Context Done,", ctx.Err()) }}運行後,如下圖所示:
可以清晰的看到,當派生出的子 Context 的 deadline 在父 Context 之後,直接返回了一個父 Context 的拷貝。故語義上等效為父。
WithDeadline 的最後期限調整為不晚於 d 返回父上下文的副本。如果父母的截止日期已經早於 d,WithDeadline (父,d) 是在語義上等效為父。返回的上下文完成的通道關閉的最後期限期滿後,返回的取消函數調用時,或當父上下文完成的通道關閉,以先發生者為準。
結構436 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {437 return WithDeadline(parent, time.Now().Add(timeout))438 }案例package main
import ( "context" "fmt" "time")
func main() { fmt.Println("嗨客網(www.haicoder.net)")
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel()
select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println("Context Timeout", ctx.Err()) }}運行後,如下圖所示:
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。
結構454 func WithValue(parent Context, key, val interface{}) Context {454 if key == nil {455 panic("nil key")456 }457 if !reflect.TypeOf(key).Comparable() {458 panic("key is not comparable")459 }460 return &valueCtx{parent, key, val}461 }案例package main
import ( "context" "fmt")
func main() { fmt.Println("嗨客網(www.haicoder.net)")
type favContextKey string
f := func(ctx context.Context, k favContextKey) { if v := ctx.Value(k); v != nil { fmt.Println("found value:", v) return } fmt.Println("key not found:", k) }
k := favContextKey("language") ctx := context.WithValue(context.Background(), k, "Go")
f(ctx, k) f(ctx, favContextKey("color"))}運行後,如下圖所示:
WithValue 返回的父與鍵關聯的值在 val 的副本。使用上下文值僅為過渡進程和 Api 的請求範圍的數據,而不是將可選參數傳遞給函數。
提供的鍵必須是可比性和應該不是字符串類型或任何其他內置的類型以避免包使用的上下文之間的碰撞。WithValue 用戶應該定義自己的鍵的類型。為了避免分配分配給接口 {} 時,上下文鍵經常有具體類型結構 {}。另外,導出的上下文關鍵變量靜態類型應該是一個指針或接口。
在 go 伺服器中,對於每個請求的 request 都是在單獨的 goroutine 中進行的,處理一個 request 也可能涉及多個 goroutine 之間的交互, 使用 context 可以使開發者方便的在這些 goroutine 裡傳遞 request 相關的數據、取消 goroutine 的 signal 或截止日期。